RowShield

Help / Security

How connection credentials are encrypted at rest

All plansLast reviewed 2026-08-23

Connecting a project gives RowShield a Postgres connection string. That credential is the most sensitive thing the product ever holds, and it is treated accordingly: envelope encryption with AES-256-GCM, a data key wrapped by an optional key held in a key-management service, and additional authenticated data that welds every ciphertext to its organisation and project.

This page describes the mechanics precisely enough to be audited. Everything named here corresponds to published code in the crypto package, because a security claim you cannot verify is marketing, not engineering.

Envelope encryption at rest

At connect time the token is encrypted with a fresh AES-256-GCM data key: a random twelve-byte initialisation vector, ciphertext, and a sixteen-byte authentication tag. The data key itself never rests in the clear — it is generated by, and wrapped under, a root key held in a KMS provider, and only the wrapped form (the edek) is stored. Deployments without a cloud KMS supply an equivalent provider; the envelope records which key identifier produced it either way.

The root key lives in a key-management service when one is configured, so the long-term secret is held by infrastructure built to guard exactly that, outside RowShield’s own storage — the envelope’s key_id names an AWS key ARN or a Cloudflare secret id. Where no external service is present, a deployment supplies a local provider behind the same narrow interface, which keeps the envelope format identical everywhere and makes moving between providers a configuration change rather than a migration of stored tokens.

The stored record is a small JSON document. Field names are stable and versioned, so older readers ignore additive fields rather than failing:

{
  "v": 1,
  "ciphertext": "…base64…",
  "iv": "…base64, 12 bytes…",
  "tag": "…base64, 16 bytes…",
  "edek": "…base64, KMS-wrapped data key…",
  "key_id": "KMS key ARN or Cloudflare secret id",
  "alg": "AES-256-GCM",
  "aad": "rowshield:v1:org=…:project=…"
}

AAD binds a ciphertext to its tenant

Additional authenticated data is authenticated but not encrypted: it travels in the clear beside the ciphertext. Here it is the string rowshield:v1:org=<organisation>:project=<project ref>, built from the identifiers of the tenant that performed the encryption.

GCM authenticates that string along with the ciphertext. Copy the row to another organisation’s context, edit the aad, or replay the blob under a different project, and the authentication tag fails before the key service is ever contacted. A ciphertext physically cannot be decrypted under another tenant — not merely discouraged by application logic, but refused by cryptography.

The corollary is worth spelling out: whoever steals rows from the application database obtains ciphertext, wrapped keys and identifiers, none of which decrypt anything without the root key — and even possession of that key yields plaintext only when the tenant context matches. Encryption here is not a compliance checkbox bolted onto storage; it is the mechanism that makes one compromised row useless for reaching any other tenant.

Decryption happens only inside the scan path, only for the organisation that owns the project, and only for the duration of the scan session. Nobody browsing support tooling needs the plaintext, so nothing in day-to-day operation requests it.

Scrubbing before logs see anything

Credentials have a habit of leaking sideways into logs through error messages. Before any text reaches RowShield’s logs, recognised credential shapes are replaced with fingerprints — the first six characters plus a length, stable enough to correlate two mentions of the same value, useless for reconstructing it.

The scrubber runs on the write path rather than trusting each callsite to remember, which is the only placement that survives contact with a growing codebase. Fingerprints are stable by construction, so the same value scrubs to the same placeholder across services and days; correlating a log line with a finding needs no secret knowledge, only the shared placeholder. The same fingerprint format appears in findings about leaked keys, described in what-we-retain.

Rotation behaves like any other credential consumer

Rotating a database credential is ordinary hygiene, and RowShield participates in it like any other client: update the stored connection string from the project settings and the next scan encrypts the replacement under a fresh envelope with its own data key. Nothing about the old envelope lingers once the project points elsewhere.

If you would rather end access entirely, disconnecting the project removes our reason to hold the credential at all, and removing the scanning role on the Postgres side closes the door regardless of what any consumer holds. Both moves are yours to make; neither requires asking us first.

Related questions

Who inside Veristria can decrypt my connection string?
No interactive path does. Decryption requires KMS access and the exact organisation-plus-project context, and occurs only in the scan worker for the duration of a scan.
Is the connection string ever written to disk unencrypted?
No. It is encrypted before storage and scrubbed from logs if it appears in error text. The plaintext exists transiently in memory during a scan session, like any database client.

Did this answer your question? If not, tell us what is missing — article corrections go straight to the person who maintains it.

RowShield checks 9 rule classes continuously. This article describes shipped behaviour only.