envdrift encrypt¶
Check or perform encryption on .env files using dotenvx or SOPS.
Synopsis¶
Description¶
The encrypt command works with dotenvx or
SOPS to manage encryption of .env files. It can:
- Check encryption status - Report which variables are encrypted/plaintext
- Detect plaintext secrets - Identify sensitive values that should be encrypted
- Perform encryption - Encrypt the file using the selected backend
Vault verification has moved to
envdrift decrypt --verify-vault. Use decrypt for drift checks; encrypt no longer supports--verify-vault.
If --backend is omitted, envdrift uses the configured backend (envdrift.toml/pyproject.toml) or defaults to dotenvx.
Safety guarantees¶
The encrypt command verifies its own outcome rather than trusting the backend's
exit code (dotenvx can exit 0 without encrypting):
- Refuses content-free files. An empty, blank-line-only, or comment-only file has no variables to encrypt, so the command declines with a non-zero exit instead of letting dotenvx scaffold a placeholder-secrets template into it.
- Refuses key stores and companion files when encrypting.
envdrift encrypt .env.keyswould encrypt the dotenvx private-key store itself — the keys become ciphertext under a brand-new keypair whose private half is never saved, permanently locking out every encrypted file in the project — so the command refuses any.keys/.example/.sample/.templatetarget by name, for every backend. The name match is case-insensitive (.env.KEYSnames the same file on macOS/Windows default filesystems), and a renamed or symlinked key store (mv .env.keys prodkeys.env) is still refused by content: a file carryingDOTENV_PRIVATE_KEY*entries is never encrypted. With--check, companions are instead reported as skipped so a pre-commit filename batch still checks its secret stores. - Handles leading-dash filenames. A file like
-dash.envis passed to dotenvx as./-dash.envso its CLI cannot misparse the name as flags (which previously fabricated a different file full of placeholder secrets). Useenvdrift encrypt -- -dash.envso envdrift's own CLI accepts the name. - Refuses filenames dotenvx cannot turn into a valid key name. dotenvx derives
DOTENV_PRIVATE_KEY_<NAME>from the filename, so a name with a space or non-ASCII character (e.g.my secrets.env,café.env) encrypts cleanly yet is permanently undecryptable. The command refuses such names up front with the plaintext intact — rename the file to use only ASCII letters, digits,.,-and_(the guard's[A-Za-z0-9._-]set, so an accented name likerésumé.envis rejected too). - Reports silent encryption failures. When the key is missing or malformed
(a
.env.keysthat is a directory, garbage, or a mismatched key), the file is re-read after the call; if any plaintext value survives, the command fails loudly instead of printing[OK].
Multiple env files can be passed in one invocation. With --check, each secret
store gets its own report, plaintext companions are skipped, and the exit code is 1
if any file should block a commit — this keeps the command usable as a pre-commit
pass_filenames: true hook, where every matched staged file is appended to a single
command line. Without --check, each file is encrypted in turn.
Arguments¶
| Argument | Description | Default |
|---|---|---|
ENV_FILE... |
Path(s) to the .env file(s) | .env |
Options¶
--check¶
Only check encryption status without modifying the file. Exits with code 1 if plaintext secrets are detected.
--schema, -s¶
Schema for better sensitive field detection. Fields marked with json_schema_extra={"sensitive": True} are checked.
--service-dir, -d¶
Directory to add to Python's sys.path for schema imports.
--backend, -b¶
Select the encryption backend (dotenvx or sops). Defaults to config or dotenvx.
SOPS Options¶
--sops-configPath to.sops.yaml--ageAge public key(s) for encryption--age-key-fileAge private key file for decryption (setsSOPS_AGE_KEY_FILE; the explicit flag overrides an ambientSOPS_AGE_KEY_FILEexport)--kmsAWS KMS key ARN--gcp-kmsGCP KMS resource ID--azure-kvAzure Key Vault key URL
Re-running encrypt on an already-encrypted SOPS file verifies the post-state: a
fully encrypted file is an honest exit-0 no-op (already encrypted (no change)),
while surviving plaintext values or recipient flags missing from the file's metadata
fail loudly. A file encrypted with the other backend is refused instead of being
double-encrypted. See the
SOPS guide.
Examples¶
Check Encryption Status¶
Output:
╭─────────────────── envdrift encrypt --check ───────────────────╮
│ Encryption Status: .env.production │
╰────────────────────────────────────────────────────────────────╯
File is partially encrypted
Variables:
Encrypted: 3
Plaintext: 5
Empty: 0
Encryption ratio: 38%
PLAINTEXT SECRETS DETECTED:
* API_KEY_BACKEND
* JWT_SECRET
WARNINGS:
* 'API_KEY_BACKEND' has a value that looks like a secret
* 'JWT_SECRET' has a name suggesting sensitive data
Recommendation:
Run: envdrift encrypt .env.production
Check with Schema¶
With a schema, envdrift knows exactly which fields are sensitive:
class ProductionSettings(BaseSettings):
DATABASE_URL: str = Field(json_schema_extra={"sensitive": True})
API_KEY: str = Field(json_schema_extra={"sensitive": True})
DEBUG: bool = False # Not sensitive
Encrypt with dotenvx¶
This will:
- Install dotenvx if
encryption.dotenvx.auto_installis enabled - Generate an ECIES (secp256k1) keypair, encrypt every value with the public key (ECIES envelope around an AES-256-GCM payload), and rewrite the file in place
- Create
.env.keyswith the matching private key (never commit this!)
Encrypt with SOPS¶
SOPS uses .sops.yaml (or the key options above) and does not create .env.keys.
Ensure the sops binary is installed or enable encryption.sops.auto_install.
CI/CD Encryption Check¶
# GitHub Actions
- name: Check secrets are encrypted
run: |
envdrift encrypt .env.production --check \
--schema config.settings:ProductionSettings
The command exits with code 1 if plaintext secrets are detected, failing the pipeline.
Encryption Report¶
The --check option provides a detailed report:
| Section | Description |
|---|---|
| Overall Status | Fully encrypted, partially encrypted, or not encrypted |
| Variables | Count of encrypted, plaintext, and empty variables |
| Encryption Ratio | Percentage of variables that are encrypted |
| Plaintext Secrets | Variables detected as secrets but not encrypted |
| Warnings | Additional concerns (e.g., credentials in URLs) |
Encryption-tool bookkeeping is excluded from the counts: dotenvx's plaintext
DOTENV_PUBLIC_KEY_* line and SOPS's sops_* metadata trailer (sops_version=,
sops_lastmodified=, the recipient key, sops_mac=, …) are neither encrypted nor
plaintext variables, so a fully SOPS-encrypted file reports as fully encrypted. A real
user variable that merely starts with sops_ (e.g. sops_token) is still counted
and scanned.
How dotenvx Encryption Works¶
envdrift integrates with dotenvx for encryption:
- Encrypted format: dotenvx uses ECIES (secp256k1 public-key encryption)
wrapping an AES-256-GCM payload. Every value is encrypted to the project's
public key and prefixed with
encrypted: - Key storage: The matching private key lives in
.env.keys(add to.gitignore); the public key is written into the encrypted file asDOTENV_PUBLIC_KEY_<ENV>so anyone can encrypt new values without the private key - Safe to commit: Encrypted
.envfiles can be committed to git
Example encrypted file:
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
DOTENV_PUBLIC_KEY_PRODUCTION="03abc123..."
DATABASE_URL=encrypted:BDQE1234567890abcdef...
API_KEY=encrypted:BDQEsecretkey123456...
DEBUG=encrypted:BDQEfalse1234567890...
#/----------------------------------------------------------/
The pinned dotenvx binary quotes the public-key value but writes ciphertext values unquoted; the example preserves that generated format.
The sensitive-field detection below powers --check warnings; it does not make
dotenvx encryption selective. For selective encryption, use
partial encryption.
How SOPS Encryption Works¶
envdrift shells out to SOPS for encryption:
- Encrypted format: Values use the
ENC[AES256_GCM,...]format - Key storage: Keys live in your SOPS setup (age, KMS, PGP, etc.)
- Config:
.sops.yamlcontrols which files and keys are used - Idempotent, but verified: Re-encrypting a file that is already fully SOPS-encrypted with the same recipients is a clean no-op (exit 0, "already encrypted (no change)"). A pre-commit hook firing twice, a CI re-run, or a documented re-run will not fail. The no-op is declared only after verifying the post-state: surviving plaintext values, or recipient flags absent from the file's metadata, fail loudly instead.
- Explicit config is validated: If you pass
--sops-config(or setsops_config_fileinenvdrift.toml) pointing at a path that does not exist, encryption fails withSOPS config file not found: <path>rather than silently falling back to an ambient.sops.yaml(which could encrypt with the wrong keys).
Sensitive Detection¶
envdrift detects sensitive values using:
Schema-based Detection¶
Fields with json_schema_extra={"sensitive": True}:
Name-based Detection¶
Variable names matching patterns:
*_KEY,*_SECRET,*_TOKEN*_PASSWORD,*_PASS*_CREDENTIAL*,*_API_KEYJWT_*,AUTH_*,PRIVATE_*,*_DSN
Value-based Detection¶
Values matching patterns:
- API keys:
sk-*,pk-*,ghp_*,gho_*,xox*-* - AWS keys:
AKIA* - Database URLs with credentials:
postgres://user:pass@... - JWT tokens:
eyJ*
Exit Codes¶
| Code | Meaning |
|---|---|
| 0 | No plaintext secrets detected (or encryption successful) |
| 1 | Plaintext secrets detected (with --check) or encryption failed |