Runtime Secret Resolution vs. Static .env Files
A side-by-side comparison of static .env files and runtime secret resolution covering security, rotation, auditability, and developer experience.
Static .env files remain the default secret delivery mechanism for most development teams. They are simple, universally supported, and require no infrastructure. They are also a persistent security liability. Runtime secret resolution — where credentials are fetched from a vault or secret manager at process start — eliminates the core risks of .env files without sacrificing developer experience. This shift is a key step on the migration path from hardcoded secrets to zero-knowledge.
How Each Approach Works
Static .env Files
A .env file contains plaintext key-value pairs in the project directory:
DATABASE_URL=postgres://user:password@host:5432/db
API_KEY=sk-proj-abc123def456
STRIPE_SECRET=sk_live_xyz789
A library like dotenv reads this file at process start and injects the values as environment variables. The file persists on disk for the lifetime of the project.
Runtime Resolution
A configuration file maps environment variable names to secret references in external vaults:
[secrets]
DATABASE_URL = { provider = "aws", ref = "sm://prod/db-url" }
API_KEY = { provider = "keychain", ref = "myapp-api-key" }
STRIPE_SECRET = { provider = "1password", ref = "op://Vault/stripe/secret" }
At process start, kz run contacts each provider, fetches the current value, and injects it into the subprocess environment. No secret value is written to disk.
Side-by-Side Comparison
| Category | Static .env Files | Runtime Resolution (KeyZero) |
|---|---|---|
| Disk persistence | Secrets stored as plaintext on disk indefinitely | No secrets on disk; resolved in memory at runtime |
| Git exposure risk | High — accidental commits are the #1 secret leak vector | None — .keyzero.toml contains references, not values |
| Secret rotation | Manual: update file, restart process, deploy | Automatic: next kz run fetches the current value from the vault |
| Rotation lag | Hours to days (requires human action + deploy) | Zero (each process start gets the latest value) |
| Access control | Filesystem permissions only | Vault-level ACLs + KeyZero CEL policies in team mode |
| Audit trail | None | Vault audit logs + KeyZero policy decision logs |
| Multi-environment | Separate files per environment (.env.production, .env.staging) | Single config with environment sections ([production.secrets]) |
| Onboarding | Copy .env from a teammate or wiki page | kz run resolves from shared vault; no secrets to copy |
| CI/CD integration | Inject via platform secrets UI or encrypted files | Resolve from vault at build/deploy time via kz run |
| AI agent safety | Secrets fully visible to agent process | Blind mode: agent sees masked tokens, not real values |
| Shared machines | Any same-user process can read the file | Secrets exist only in the subprocess environment |
Security Implications in Detail
Disk Persistence
A .env file is a plaintext file on the filesystem. It survives process restarts, system reboots, and user sessions. It is readable by any process running as the same user. It appears in filesystem backups, Time Machine snapshots, and disk images.
Runtime resolution eliminates disk persistence entirely. The secret value exists only in the subprocess's environment memory and is released when the process exits.
Rotation Lag
When a secret is rotated in the source of truth (a vault, a cloud provider console, a password manager), .env files in every environment must be manually updated. The window between rotation and deployment — rotation lag — is the period during which the old credential is still in use and potentially compromised.
With runtime resolution, the next process start automatically fetches the new value. There is no rotation lag for new process instances. For long-running processes, KeyZero's team mode supports TTL-based re-resolution.
Access Control
.env files have one access control mechanism: filesystem permissions. Anyone who can read the file gets every secret in it. There is no per-secret access control, no role-based restriction, and no policy evaluation.
KeyZero's team mode (kz server start) evaluates CEL policies against JWT-verified identity for every secret request. A CI runner can be granted access to deployment secrets but denied access to production database credentials, even when both are in the same vault. KeyZero supports backends including 1Password and HashiCorp Vault, so you can resolve from whichever provider fits your infrastructure.
Auditability
.env files produce no audit trail. There is no record of who read the file, when, or which secrets were accessed.
KeyZero logs every resolution request with the caller's verified identity, the requested resource, the policy decision, and the timestamp. In team mode, these logs integrate with existing SIEM and observability infrastructure.
Developer Experience Comparison
Initial Setup
.env approach:
# Ask teammate for secrets, or copy from wiki
cp .env.example .env
# Manually fill in values
vim .env
KeyZero approach:
kz init # Creates .keyzero.toml interactively
kz add # Add secret mappings
kz put --missing # Store values in your provider
kz run -- npm start # Run with secrets
The KeyZero setup is slightly more involved initially, but it eliminates the "ask a teammate for the .env file" workflow permanently. New team members run kz run and secrets resolve from the shared vault.
Day-to-Day Usage
With .env, you run your process normally — npm start, python app.py — and dotenv handles loading. With KeyZero, you prefix with kz run --:
kz run -- npm start
Alternatively, the shell hook auto-loads secrets when you cd into a project directory:
eval "$(kz hook --shell zsh)"
With the shell hook active, secrets are loaded automatically and the workflow is identical to .env.
Environment Management
.env requires separate files per environment: .env, .env.staging, .env.production. Each must be maintained independently.
KeyZero uses a single .keyzero.toml with environment sections:
[secrets]
DATABASE_URL = { provider = "keychain", ref = "myapp-db-url" }
[production.secrets]
DATABASE_URL = { provider = "aws", ref = "sm://prod/db-url" }
Set KEYZERO_ENV=production to activate production secrets. The same config file works across all environments.
How to Migrate from .env Files
Migration from .env to KeyZero is incremental. You do not need to convert everything at once.
Step 1: Install KeyZero
brew install getkeyzero/tap/keyzero
# or: cargo install keyzero
# or: npx @anthropic/keyzero (via npm)
Step 2: Initialize Config
kz init
This creates .keyzero.toml in your project root.
Step 3: Map Existing Secrets
For each entry in your .env, add a mapping to .keyzero.toml:
[secrets]
DATABASE_URL = { provider = "keychain", ref = "myapp-db-url" }
API_KEY = { provider = "keychain", ref = "myapp-api-key" }
Step 4: Store Values
kz put --missing
KeyZero prompts you for each secret that does not yet exist in the provider.
Step 5: Verify
kz run -- env | grep DATABASE_URL
Step 6: Remove .env
Once verified, delete the .env file and add .keyzero.toml to version control (it contains only references, not values). Add .keyzero.local.toml to .gitignore for personal overrides.