Secrets & Encryption
How values are stored, versioned, and read.
Encryption at rest
Every secret value is sealed with AES-256-GCM before it's written to Postgres — a single encryption key, held only by the API process, never by the database. The database sees ciphertext; it has no way to recover the plaintext on its own.
Decryption happens only in memory, on demand, for a request that has already passed the access-grant check for that environment. A secret is never decrypted "just in case" — only when a specific, authorized read actually needs it.
Version history
Updating a key doesn't overwrite it — it inserts a new version and repoints the key at it. Nothing is lost:
- Every version is independently encrypted and stored.
envi diffandenvi pushreason about the current version, but the history exists for anyone who needs to answer "what was this value before?"- Deleting a key is a soft delete — it stops appearing in reads, but its history isn't destroyed.
Every read is audited
Reading a secret's value — via the CLI, the dashboard, or a service token — writes an audit event: who, what, when. See the audit log in the dashboard, or envi activity from the CLI.
One read of an environment is one event, recording how many secrets it covered, whether it came from envi pull, envi run, the dashboard, or a service token in CI. The unit is the act of reading, not the key count, so a command you run fifty times a day stays legible in the feed instead of drowning it.
Injection, and what it does not protect against
envi run -- npm start fetches the environment's secrets and hands them to the child process directly, so no .env is written. envi mod does the same for editing them: the values are fetched into a terminal editor and pushed back, never touching the filesystem. When you do want the file, envi push --clean removes it again once it is up. That removes the longest-lived exposure in the normal workflow: a plaintext file that outlives the process that needed it, gets copied into build contexts and editor backups, and only stays out of version control as long as the .gitignore is right.
Injection is not a sandbox. A child process's environment is readable by the same user on the same machine — through /proc/<pid>/environ on Linux, or ps eww — and anything the process itself does with a value, such as logging it or sending it onward, is outside Envi's reach. What injection buys is that the secrets exist for the life of one process and are never written to disk.
What the key protects against
AES-256-GCM at rest protects against a very specific threat: someone with read access to the database, but not the encryption key, cannot recover secret values. That covers a leaked database backup, a misconfigured read replica, or a compromised database credential that doesn't also expose the API's environment.
It does not protect against a compromised API process itself, or against someone who already has legitimate read access to the environment through Envi. For that, see Access & Permissions.

