Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

Encrypted .env files

varsafe export writes a .env with every value encrypted to its environment, so the file is safe to commit, and varsafe run --env-file opens it for anyone allowed to read that environment.

CLI v7.8.1

Only the values are encrypted; the variable names stay readable. A file that ends up in a backup, a shared drive, a chat thread or a git history gives away which settings exist, never what they are.

Plaintext is always an explicit choice: you have to ask for it with --plain.

Quick start

Set VARSAFE_PROJECT_ID to your project ID. Pass it with -i so these commands keep working if the project is renamed.

Export the environment's secrets, encrypted

varsafe export -i "$VARSAFE_PROJECT_ID" -e development -o .env

Commit the file

The file is safe to commit:

git add .env

Open it and hand its values to a command

varsafe run -i "$VARSAFE_PROJECT_ID" -e development --env-file .env -- npm run dev

Exporting and opening need the same access as reading the environment’s values: a varsafe login with the default Standard access, or an API token that reaches the environment, read-only included. So a teammate who clones the repository opens the file with their own login, and a pipeline with its token. A login approved as Read only (names only), or a role that cannot read the environment, can do neither.

The first export from an environment creates its key. Only the .env format is encrypted:

Export Values in the file
.env, the default Encrypted
.env with --plain Plaintext
--format docker, json or yaml Always plaintext

One file per environment

varsafe export -i "$VARSAFE_PROJECT_ID" -e staging -o .env.staging
varsafe run -i "$VARSAFE_PROJECT_ID" -e staging --env-file .env.staging -- npm run dev

A file opens only under the environment it was exported from, because the key it names belongs to that environment. Run it against another one and the run stops with an error instead of injecting nothing.

--env-file can be repeated. File values win over the environment’s current values, and a later file wins over an earlier one.

What is in the file

#@varsafe/v2/ek_a1b2c3d4
DATABASE_URL="varsafe:v2:base64url-encoded-ciphertext..."
API_KEY="varsafe:v2:base64url-encoded-ciphertext..."
  • The header line, #@varsafe/v2/ek_..., names the format version and the environment key the file was encrypted with.
  • Each value is varsafe:v2: followed by the encrypted payload.
  • The file holds no key material. varsafe keeps the environment’s keys on its servers, with the private keys encrypted by a key management service, and no private key is handed out: varsafe run --env-file sends the encrypted values to varsafe, which opens them for a caller allowed to read the environment and records the read in the audit log, by variable name.

What encryption gives you, and what it does not

Each value is encrypted separately, with X25519 key exchange, HKDF-SHA256 key derivation and AES-256-GCM, using a fresh one-time key per value. Each encrypted value is also bound to the key it was encrypted with and to the variable name it sits under.

What that guarantees:

  • Unreadable without varsafe. Someone who finds the file learns the variable names and nothing else.
  • One value tells you nothing about another. Each value is encrypted on its own.
  • Values cannot be moved around. An encrypted value copied onto a different variable name, duplicated onto another variable, or swapped in from a file encrypted with a different key is refused, and the whole file with it. Nothing from a tampered file is injected.
  • No silent downgrade. A file that claims the varsafe format but is damaged, partly encrypted, or from an unknown version is refused outright, never read as plaintext.

What it does not:

  • It does not prove who wrote a line. Encrypting uses the public key, so anyone who can read the environment’s secrets can produce a valid encrypted value for any variable name. Review changes to an encrypted .env the way you review any configuration change: a changed value is a changed secret, even though you cannot read it.
  • It is a snapshot, not a live view. A file holds the values as they were when it was exported. Anyone allowed to read the environment can open it, including values since changed or deleted in varsafe, for as long as the key that encrypted it exists. Changing or deleting a secret does not change what an older file opens to.
  • It does not prove the file is complete or current. Each value is tamper-evident, the file as a whole is not: a removed line, or an older copy of the file put back, still opens.
  • It does not take back a copy. Anyone who was allowed to open the file has seen its values. After a suspected exposure, rotate the secrets themselves, not only the key.

Rotated and deleted keys

When an environment’s key is replaced, files encrypted with the old key keep opening. varsafe run --env-file then re-encrypts the file to the new key, so the next commit carries it.

Deleting an environment’s keys means no file encrypted to them can be opened again, by anyone. The next export creates a new key. Only an owner or admin in an interactive session can rotate or delete keys, and today only through the API: the dashboard has no control for it yet. API tokens, CLI logins and agents are refused.

When someone loses access

varsafe no longer hands out an environment’s private key: it opens files itself, and only for someone still allowed to read the environment’s values. A person who leaves the team, or is moved to a role that cannot read an environment, can no longer open its files through varsafe, copies they kept included, so nothing needs rotating when someone leaves. What they read while they had access stays known to them, as with any secret, so rotate the secrets themselves if that matters. Deleting the environment’s keys means varsafe opens no file encrypted with those keys.

Versions before July 2026 let an owner or admin download an environment’s private key. Whoever did can still decrypt, offline, the files encrypted with that key, and neither leaving the team nor deleting the key changes that; rotate the secrets those files hold if that matters.

When a tool needs a readable file

Most tools read configuration from the environment, and varsafe run hands secrets to them without writing a file. When a tool requires a file, such as docker compose --env-file, use this Linux-only recipe with /dev/shm mounted as tmpfs:

(
  env_file=/dev/shm/varsafe-$$.env
  trap 'rm -f "$env_file"' EXIT
  varsafe export -i "$VARSAFE_PROJECT_ID" -e production --plain --tmpfs -o "${env_file##*/}" &&
    docker compose --env-file "$env_file" up -d
)

Compose uses this file only to fill in ${VAR} references in compose.yaml. --tmpfs writes to /dev/shm/ plus the file name from -o, discarding any directory part. It creates and mounts nothing, so where /dev/shm does not exist, as on macOS, the export fails and && skips the next command. The file is 0600, readable by you only. The process ID in the name keeps two jobs running as the same user on one runner from reading or deleting each other’s file. The && prevents Compose from reading a stale export when export fails. The trap deletes the file when the subshell exits, including when Compose fails.

On macOS, prefer varsafe run without a file. If a file is required, omit --tmpfs and put the file under a private mktemp -d directory and remove that directory with an exit trap, as in the CI temporary-directory recipe. This file can reach disk.