K0KEYZERO
← All posts

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.

·secret-management, env-files, security

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

CategoryStatic .env FilesRuntime Resolution (KeyZero)
Disk persistenceSecrets stored as plaintext on disk indefinitelyNo secrets on disk; resolved in memory at runtime
Git exposure riskHigh — accidental commits are the #1 secret leak vectorNone — .keyzero.toml contains references, not values
Secret rotationManual: update file, restart process, deployAutomatic: next kz run fetches the current value from the vault
Rotation lagHours to days (requires human action + deploy)Zero (each process start gets the latest value)
Access controlFilesystem permissions onlyVault-level ACLs + KeyZero CEL policies in team mode
Audit trailNoneVault audit logs + KeyZero policy decision logs
Multi-environmentSeparate files per environment (.env.production, .env.staging)Single config with environment sections ([production.secrets])
OnboardingCopy .env from a teammate or wiki pagekz run resolves from shared vault; no secrets to copy
CI/CD integrationInject via platform secrets UI or encrypted filesResolve from vault at build/deploy time via kz run
AI agent safetySecrets fully visible to agent processBlind mode: agent sees masked tokens, not real values
Shared machinesAny same-user process can read the fileSecrets 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.