NEWNow open source & self-hostable. Star us on GitHub →
·12 min·By Nicolas Ritouet

Are .env Files Still Safe for Secrets in 2026?

Environment variables sit in plain text, leak through logs, crash dumps, Docker layers, and AI agents. Here's what to use instead — with migration examples.

TL;DR: Your .env file stores secrets in plain text. It leaks through logs, crash dumps, Docker layers, ps output, child processes, and — since 2025 — AI coding agents that read your entire project directory. The fix isn't better .gitignore rules. It's keeping secrets out of files entirely.


The short answer: no

Environment variables were never designed for secrets. They were designed for configuration — things like LOG_LEVEL=debug or NODE_ENV=production. Somewhere along the way, the twelve-factor app methodology told us to put everything in env vars, and we started storing database passwords next to log levels.

In 2026, the attack surface around .env files has expanded dramatically. Here's what's changed.

7 ways your .env file leaks secrets

1. AI coding agents read your filesystem

This is the newest and most underestimated risk. AI coding agents load your entire project directory into their context window — including .env files.

Your API keys, database passwords, and JWT secrets are sent to LLM providers as part of the conversation. .gitignore doesn't help: these tools read the filesystem directly, not the git index.

Research from Knostic confirmed that Claude Code loads .env files without explicit permission. Cursor and GitHub Copilot use the same filesystem-based approach — they index your project directory to provide context. A Wiz study found AI-assisted code regularly leaks secrets in public repositories.

2. Logs and crash dumps capture them silently

Environment variables appear in places you don't expect:

  • Error stack traces in production
  • Crash dump files
  • Debug logs when frameworks log the full request context
  • APM tools that capture process metadata

The Seneca framework vulnerability (CVE-2019-5483) exposed environment variables in error messages, revealing cloud API keys in publicly accessible crash reports. This isn't an isolated case — it's a pattern.

3. Process listings expose them

On any Unix system, environment variables are visible through ps and /proc:

# Anyone on the system can see your secrets
cat /proc/<pid>/environ

# Or via a path traversal vulnerability
curl http://your-app.com/public/../../../../proc/self/environ

This has been exploited in real attacks. If an attacker gains read access to the filesystem — even through a minor vulnerability — your secrets are exposed.

4. Docker images embed them permanently

Approximately 10% of images on Docker Hub leak sensitive data. Common mistakes:

# WRONG — secrets visible in image history
ENV DATABASE_URL=postgres://user:pass@host/db
COPY .env /app/.env

# Anyone can extract them
docker history your-image --no-trunc

Build arguments (ARG) aren't better — they persist in image layers. Even if you delete the .env file in a later layer, it exists in the build cache.

5. Child processes inherit everything

When you spawn a subprocess, it inherits all parent environment variables by default:

// Every secret is passed to this child process
const child = exec('some-command')
// The child process inherits all of process.env:
// DATABASE_URL, STRIPE_KEY, JWT_SECRET...

This violates the principle of least privilege. A logging script doesn't need your Stripe API key, but it gets it anyway.

6. No access control or audit trail

Environment variables have no concept of:

  • Who accessed a secret
  • When it was last rotated
  • Which processes actually need it
  • Whether it's been compromised

There's no way to revoke a single variable without restarting the entire process. There's no history, no versioning, no alerts.

7. They don't rotate

Static secrets are the most dangerous kind. The GitGuardian 2025 State of Secrets Sprawl report found that 70% of secrets leaked as far back as 2022 are still active today. If a key in your .env file was compromised two years ago, it probably still works.

The hybrid approach isn't enough

Doppler's take on this recommends a "hybrid approach": keep non-sensitive config in env vars, move secrets to a manager. That's solid advice. Tools like Doppler, Infisical, and Keyway all support runtime injection (doppler run, infisical run, keyway run) that injects secrets into process memory without writing files.

But many teams still export secrets to .env files for local development:

# Common pattern — downloads secrets to a file for local use
doppler secrets download --no-file --format env > .env.local
infisical export > .env.local

The moment secrets land in a file, you're back to square one. Your AI coding agent can read them. Your Docker build can embed them. The file can be committed accidentally.

The zero-disk approach

The real fix is to never write secrets to disk at all. Inject them directly into process memory at runtime:

# Keyway — secrets exist only in process memory
keyway run -- npm start
# 3 secrets injected. No file on disk. AI agents see nothing.
# When the process stops, secrets vanish.

This eliminates every attack vector we've discussed:

Attack vector.env fileSecrets manager (file export)Zero-disk injection
AI agent reads filesystemExposedExposedSafe
Logs / crash dumpsRiskRiskRisk*
Process listing (/proc)ExposedExposedExposed**
Docker image layersRiskRiskSafe
Accidental git commitRiskRiskSafe
Child process inheritanceExposedExposedExposed**
No rotation / auditNo auditAudit availableAudit available

* Log redaction (e.g., Pino's redact option) mitigates this. ** Process memory is still accessible to root, but there's no file to accidentally share, commit, or embed.

Zero-disk doesn't solve everything, but it eliminates the most common leak vectors: files on disk that get committed, copied, embedded, or read by AI tools.

When .env files are still fine

Not everything needs a secrets manager. The rule is simple:

If the value leaked, could it grant access or cause damage?

  • Yes → secrets manager: DATABASE_URL, STRIPE_SECRET_KEY, JWT_SECRET, AWS_ACCESS_KEY_ID
  • No → env var is fine: LOG_LEVEL, NODE_ENV, NEXT_PUBLIC_APP_URL, PORT

For non-sensitive configuration, .env files are perfectly adequate. The problem is mixing configuration and secrets in the same file.

Migration: from .env to zero-disk in 10 minutes

Step 1: Audit your .env file

Separate secrets from configuration:

# .env.local (BEFORE)
DATABASE_URL=postgres://user:pass@host/db    # ← secret
STRIPE_SECRET_KEY=sk_live_...                 # ← secret
JWT_SECRET=super-secret-key                   # ← secret
LOG_LEVEL=debug                               # ← config (safe)
NEXT_PUBLIC_APP_URL=http://localhost:3000      # ← config (safe)
PORT=3000                                     # ← config (safe)

Step 2: Move secrets to a manager

# Push secrets to Keyway
keyway init
keyway set DATABASE_URL "postgres://user:pass@host/db"
keyway set STRIPE_SECRET_KEY "sk_live_..."
keyway set JWT_SECRET "super-secret-key"

Step 3: Keep only config in .env

# .env.local (AFTER — no secrets)
LOG_LEVEL=debug
NEXT_PUBLIC_APP_URL=http://localhost:3000
PORT=3000

Step 4: Run with injection

# Secrets from Keyway + config from .env
keyway run -- npm start

Your .env file now contains zero secrets. AI agents, Docker builds, and accidental commits can't leak what isn't there.

Validate at startup

Whether you use .env files or a secrets manager, always validate that required variables exist before your app starts:

import { z } from 'zod'

const env = z.object({
  DATABASE_URL: z.string().url(),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
  LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
}).parse(process.env)

This catches missing variables at startup instead of at 3 AM when a customer hits the one code path that needs the missing key.

Security checklist

Before you ship:

  • No secrets in .env files — only non-sensitive configuration
  • .env and .env*.local in .gitignore
  • Pre-commit hook scanning for leaked secrets
  • Secrets injected at runtime via secrets manager
  • Startup validation with Zod or equivalent
  • Log redaction for sensitive headers and tokens
  • Secret rotation process documented and automated
  • AI coding tools can't access secrets (zero-disk approach)

Further Reading


Your .env file was a reasonable solution in 2015. In 2026, with AI agents reading your filesystem and Docker images leaking secrets at scale, it's a liability. The fix isn't complicated — it's just different from what we're used to.

Stop sharing secrets on Slack

Keyway syncs your environment variables securely. Free for open source.