turboEnv

Envelope encryption, and what "encrypted at rest" actually promises

Every product that stores secrets says it encrypts them at rest. The phrase is close to meaningless on its own, because it describes a mechanism rather than a threat model. What matters is which key encrypts what, who holds it, and which attacks that arrangement actually stops.

This is how envelope encryption works, and what it is worth.

The naive version, and its problem

The obvious design is one key for everything. You hold a master key, you encrypt every value under it, you store the ciphertext.

This works, and it has two problems that only appear later.

The first is rotation. Good practice says you should be able to rotate that key — after a suspected exposure, after someone with access leaves, or just on a schedule. With one key over everything, rotating means decrypting every value in the database and re-encrypting it. For a few thousand rows that is a slow migration. For a few million it is an outage.

The second is blast radius. One key protects everything, so one leaked key exposes everything. There is no unit smaller than "the whole database".

The envelope

Envelope encryption adds one layer of indirection:

MASTER_KEY                      held outside the database
  wraps → per-project DEK       stored, encrypted, in the database
    encrypts → each value       stored as ciphertext

Each project gets its own data encryption key, or DEK, generated when the project is created. Values are encrypted under their project's DEK. The DEK itself is encrypted — wrapped — under the master key, and the wrapped form is what sits in the database next to the project row.

To read a value: fetch the wrapped DEK, unwrap it with the master key, decrypt the value.

Both problems dissolve.

Rotation becomes cheap. To rotate the master key, unwrap each project's DEK with the old key and re-wrap it with the new one. That is one small write per project — thousands of rows, not millions — and the values themselves are never touched, because they are still encrypted under the same DEKs they always were.

Blast radius shrinks. A DEK that leaks exposes one project. The master key is the only thing that exposes everything, and it is the one value that never goes near the database.

This is not a novel design. It is what AWS KMS, Google Cloud KMS, and most managed secret stores do internally. It is worth understanding rather than treating as a black box, because the properties it gives you are specific.

What this actually protects against

Being precise here matters more than the algorithm names.

It stops an attacker who reaches your storage. A dumped database, a stolen backup, a misconfigured snapshot, a decommissioned disk. All of these yield ciphertext plus wrapped DEKs, and neither is useful without the master key, which lives somewhere else — on turboEnv, a Cloudflare Worker secret, which is not in the database and not in the repository.

It stops accidental exposure through the data layer. A logging misconfiguration that dumps rows, an analytics tool pointed at a replica, a support engineer with read access to the database. They see ciphertext.

It does not stop an attacker who reaches your application. Code running with access to the master key can decrypt anything it is allowed to fetch. That is not a flaw in the scheme; it is the necessary consequence of the server being able to serve values at all.

It does not make the operator unable to read your data. This is the one that marketing copy tends to blur. If a hosted service holds the master key, the people running that service are technically capable of decrypting your values. They may have controls, policies and audit trails around that capability, and those are worth something. But "encrypted at rest" is not "we cannot read it".

End-to-end encryption is what "we cannot read it" requires, and it means the key never leaves your machines — which in turn means the web UI cannot show you a value, the server cannot help a client that lost its key, and you own the consequences of losing it. That is a real trade-off, not an obviously better choice, and it is why most secret managers do not make it.

The honest summary for any hosted service, including turboEnv, is: encryption at rest protects you from the storage layer, and the operator is in your trust boundary. If your threat model does not permit that, you need a tool that keeps the key on your own machines.

Details that matter in the implementation

A few specifics separate a real implementation from one that merely uses the right words.

AES-256-GCM, not AES-CBC. GCM is authenticated: tampering with the ciphertext makes decryption fail rather than silently produce different plaintext. Unauthenticated modes have a long history of being manipulated by attackers who cannot read the data but can change it.

A fresh IV for every value. Reusing an initialisation vector with GCM under the same key is catastrophic — it can leak the plaintext and, worse, the authentication key. Every encryption must generate a new random IV and store it alongside the ciphertext. The IV is not secret; the uniqueness is what matters.

A separate DEK per tenant boundary. Per project rather than per user or per whole database. It should be small enough that one compromised key is a contained incident.

No key material in logs, errors, or the version history. The most common way key material escapes is not cryptanalysis. It is an exception handler that serialises too much.

Reading the claim

Next time a service tells you your secrets are encrypted at rest, the useful questions are short:

A service with a good answer will tell you plainly. turboEnv's answers are in the privacy policy, and the API it exposes is described in full in the OpenAPI specification.