Where the keys live
How Thaw encrypts your documents: a key per file, a key per account, a master key that never leaves the key service, and what that buys you.
“Encrypted at rest” is a phrase every product uses and almost none explain. Here is the explanation for Thaw, in enough detail to be checked.
Three layers
The master key is a customer-managed key in AWS KMS. It never leaves the service. Nothing we run can read it; we can only ask KMS to use it, and every such use is logged by AWS.
Each account has its own key, generated by KMS and handed back wrapped by the master. The app unwraps it for the duration of a request or a job and holds it in memory only.
Each document file has its own key, generated by the app and wrapped by the account key. Files are encrypted in one-megabyte chunks before they touch storage, each chunk authenticated against the file header, its position and whether it is the last one, so a file of any size streams in and out without being held in memory and a chunk cannot be moved or dropped without detection.
Columns that hold anything a person wrote or anything that identifies one (titles, summaries, names, file names, facts, IP addresses in the audit log) are sealed with the account key and bound to their column name.
What this buys you
Deletion is real. Deleting a document destroys its key immediately. The stored bytes are noise from that second, before any backup rotates. Deleting an account destroys the account key, which does the same for everything in it at once.
A stolen database is not your documents. Without KMS, the wrapped keys are useless, and KMS use is logged and scoped to the application’s role.
Identifiers never reach an index. Facts with the identifier role (Social Security, account, policy, license, VIN and confirmation numbers) get a blind index, a keyed hash of the normalized value under a key derived from your account key, so an exact search still works. They are redacted from page text before it is chunked and indexed. The full-text index is derived from redacted text.
What is derived and disclosed
Two things are not encrypted in the column, on purpose, and we would rather say so than imply otherwise: the full-text search vectors over redacted chunk text, and later the embedding vectors. They are derived from text with identifiers removed, and they are what makes search work. The chunk text itself is encrypted.
Isolation underneath
Every tenant table carries the account id. The application scopes every query to it. PostgreSQL row-level security is forced on every tenant table underneath that, and the app connects as a role that cannot bypass it. A test in the suite runs a deliberately unscoped query from one account and asserts it sees nothing of another’s.
The audit log
Sign-ins, views, downloads, corrections and key operations go into an append-only log. Each row carries a hash of its content and the previous row’s hash, written under a per-account lock, and the database refuses updates and deletes on the table. If a row were changed or removed, the chain would not verify.
None of this is exotic. It is the arrangement you would choose for your own passports if you were building it for yourself, which is how it happened.