Env vars, dotenv, and shells
Why convenient delivery becomes permanent exposure.
In short. Environment variables are the most popular secret delivery mechanism in modern apps — and one of the leakiest.
Environment variables are the most popular secret delivery mechanism in modern apps — and one of the leakiest. They are easy to wire in containers and twelve-factor apps, visible to anything that can read process state, often dumped into crash reports, and frequently copied into .env files that get committed "just for local dev."
This lesson is not "never use env vars." It is "know what you are buying." Prefer files with tight permissions or platform-native secret mounts for high-value credentials, keep env for low-risk config, and never assume env equals private.
How env becomes public
On Linux, /proc/<pid>/environ is readable by the same user (and root). ps e can print env on some systems. Child processes inherit env unless scrubbed. Language runtimes mirror env into crash dumps and debug endpoints. Container orchestration surfaces env in API objects — kubectl get deploy -o yaml may show them to anyone with RBAC read.
# demonstrate inheritance and visibility (lab process)export DEMO_SECRET=supersecretnode -e 'console.log(process.env.DEMO_SECRET)'tr '\0' '\n' < /proc/self/environ | grep DEMO_SECRET || true
supersecretDEMO_SECRET=supersecret# SUCCESS — same user can read environ
dotenv files
.env files help local development and destroy production safety when copied into images or git. Add .env to .gitignore and also to secret scanners — ignore files are not a security boundary. Prefer .env.example with empty placeholders. In CI, inject secrets from the platform store, not from committed files.
.env.env.*!.env.example*.pemcredentials.json
Going deeper
Orchestrators amplify env risk. Kubernetes Secrets projected as env appear in pod specs visible to anyone who can get pods. ECS task definitions and serverless environment configurations likewise become API-readable secrets. Prefer volume mounts or runtime fetches for high-value credentials.
Shell history is another env cousin: export SECRET=... typed by hand lands in ~/.bash_history. Disable history for break-glass sessions or use interactive secret entry that never hits argv. Never pass secrets on process command lines — ps will show them.
Twelve-factor apps popularized env config for portability. Keep using env for non-secret config (log level, feature flags). Split "config" from "credentials" in code review checklists so convenience does not drag keys into the porous channel.
Try this
Start a process with a secret in env, then read it back from /proc or an equivalent on your OS. Then move the same secret to a 0600 file and confirm ps/environ no longer reveals it.
echo supersecret > /tmp/demo.secret && chmod 600 /tmp/demo.secretexport DEMO_SECRET=node -e 'const fs=require("fs"); console.log(fs.readFileSync("/tmp/demo.secret","utf8"))'tr '\0' '\n' < /proc/self/environ | grep DEMO_SECRET || echo 'no DEMO_SECRET in environ'
supersecretno DEMO_SECRET in environ# SUCCESS — file delivery keeps environ clean
Takeaway
Env vars are convenient and porous. Use them deliberately, keep .env out of git and images, and prefer file or manager-backed delivery for credentials that matter.
Next: git history — where "deleted" secrets go to live forever.