GUIDE · COMPARISON 8 min ·

dotenv-vault and dotenvx, honestlyWhat they solve. Where they stop.

Mot's two tools changed how .env files travel. Neither changes what happens after decryption. An agent that inherits your decrypted env sees the same thing it saw before.

TL;DR· the answer, in twenty seconds

What: dotenv-vault syncs .env files across environments via a SaaS vault, one DOTENV_KEY per environment. dotenvx is the OSS successor: it encrypts .env files so you can commit them to git, with a per-environment public/private key pair. Both decrypt the full secret set into process environment at startup.

The gap: decrypting into env hands the whole payload to every child process, including coding agents. Run dotenvx run -- claude code and the agent sees your production database URL, your Stripe key, and anything else in that file. If the agent persists environment state (as Claude Code did before the February 2026 Anthropic patch), decrypted values land in a state file on disk.

The lesson: encrypted storage solves the supply-chain problem (secrets in plaintext on disk or in git). It does not solve the runtime problem (secrets visible to every process that runs inside the decrypted context). Those are different attack surfaces that need different mitigations.

Mot (Motdotla, Scott Motte) built dotenv. Then he built dotenv-vault to solve the sync problem. Then he deprecated dotenv-vault and built dotenvx to solve what he saw as the root issue: secrets living as plaintext on developer machines and in CI environments. Both tools are real, used by real teams, and represent genuine progress on a real problem.

This piece looks at what each tool actually does, where the model stops working, and what that means for teams running coding agents.

The short answer: if your threat model is "I don't want plaintext secrets committed to git or stored unencrypted on developer laptops," dotenvx handles it. If your threat model extends to "I don't want an AI agent to read secrets it doesn't need during a coding session," dotenvx does not help.

Short version

  • dotenv-vault is a SaaS sync service. You push your .env files to Mot's hosted vault, get a DOTENV_KEY per environment, and the library decrypts the right file at process start. It's still a live service; the free tier exists but team features cost money.
  • dotenvx is MIT-licensed OSS. It encrypts .env files in place using a public/private key pair per environment. You commit the encrypted files. The private key lives in the developer's environment or a CI secret. dotenvx run -- node server.js decrypts and injects at process start.
  • Both tools decrypt into process environment variables. The full secret set becomes visible to the running process and all its children.
  • An AI coding agent spawned inside that process context inherits the whole decrypted env. The encryption helps nothing after that point.
  • The private key is the new plaintext secret. Where it lives matters as much as where the encrypted file lives.

dotenv-vault: what it actually does

dotenv-vault predates dotenvx and has a different architecture. You connect a project to the hosted vault at dotenv.org, push environment files for each target (development, staging, production), and the service generates a per-environment DOTENV_KEY. Your CI or deployment pipeline sets that key as an environment variable. At runtime, the dotenv-vault library fetches and decrypts the correct file.

# Install and link a project
npm install dotenv-vault
npx dotenv-vault new
npx dotenv-vault push

# In CI, set DOTENV_KEY from your vault dashboard
# At runtime, the library decrypts on require('dotenv-vault').config()

The practical result: secrets leave developer laptops and go into one hosted store. Team members pull the current values without emailing .env files around. Rotation happens in one place. The audit story is better than .env files copied across Slack.

The cost: you're dependent on dotenv.org being up. The free tier works for solo projects; team features (multiple environments, access controls, audit logs) require a paid plan that scales with team size. Mot has been clear that dotenv-vault is the older product. He's not actively deprecating it, but new development is in dotenvx.

One real downside that doesn't get mentioned often: the DOTENV_KEY for a given environment is a single credential that decrypts all secrets in that environment. Rotate one secret and you need to re-push the full environment. The key itself becomes a high-value target. If DOTENV_KEY lands in a CI log, a Docker image layer, or a published state file, the vault protection collapses. The key needs the same protection as the secrets it unlocks, which returns you to the original problem of distributing a sensitive value across a team.

dotenvx: the OSS encrypted-file approach

dotenvx dropped in 2024 and took a different position. No hosted service. The encrypted .env files live in your repo, like SOPS does for arbitrary files. Each environment gets its own .env.production, .env.staging, etc. The public key for each file is stored in the file's header, readable by anyone with the repo. The private key is not in the repo; you set it as DOTENV_PRIVATE_KEY_PRODUCTION in the developer's shell or as a CI secret.

# Encrypt an existing .env.production file
dotenvx encrypt -f .env.production

# Run with decryption injected at process start
dotenvx run -f .env.production -- node server.js

The resulting .env.production looks like:

#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/         public-key encryption for .env files            /
#/       [how it works](https://dotenvx.com/encryption)    /
DOTENV_PUBLIC_KEY="036b53c9..."

# .env.production
DATABASE_URL="encrypted:BFqvwV5kT..."
STRIPE_SECRET_KEY="encrypted:BKx9mN2pL..."

You commit that file. A developer with the private key in their shell runs dotenvx run and gets a decrypted process. A developer without the key can't decrypt it. CI pipelines inject DOTENV_PRIVATE_KEY_PRODUCTION as a secret and decrypt at build time.

This is the right model for several real problems. It eliminates plaintext secrets from the git history. It eliminates the "who has the current staging key" problem that plagues teams relying on .env files in Dropbox or Notion. The private key is a single rotating credential, not a secret per variable.

When dotenvx beats SOPS

SOPS (Mozilla's Secrets OPerationS) does the same encrypted-file-in-git concept for arbitrary file formats: YAML, JSON, INI, binary. If you already use SOPS with AWS KMS or age keys, dotenvx doesn't add much.

Where dotenvx wins:

  • You specifically want .env-shaped files. Existing tooling that reads .env format (including most Node.js frameworks and the dotenv library itself) works without changes.
  • You want per-environment key isolation without external KMS infrastructure. dotenvx generates the key pair locally; no AWS account, no Vault server.
  • You want a simple upgrade path from unencrypted dotenv files. dotenvx encrypt works on existing files in-place.

If you're starting fresh and want to encrypt Kubernetes manifests, Terraform vars, and .env files in one workflow, SOPS with age keys is the more general tool.

Where the model stops working for AI coding agents

dotenvx solves the storage problem. The runtime problem is different.

When you run dotenvx run -f .env.production -- <your dev environment>, the private key decrypts all secrets in that file and hands them to the process as ordinary environment variables. Every subprocess spawned by that process inherits the full set. That includes a coding agent.

# This decrypts .env.production and exposes every value to claude code
dotenvx run -f .env.production -- claude code

From the agent's perspective, there's no difference between this and export STRIPE_SECRET_KEY=sk_live_... && claude code. The decryption happened upstream. The agent sees plaintext values in its inherited environment.

Two problems follow from that.

The private key sits somewhere. On a developer laptop, the private key lives in ~/.zshrc or is exported in the session shell. Any process with access to the developer's environment (including the agent) can read it if DOTENV_PRIVATE_KEY_PRODUCTION is exported. That key decrypts the encrypted file in git for anyone who has both pieces.

Agents may persist decrypted values. The February 2026 Anthropic patch addressed Claude Code writing environment variables into settings.local.json. Before that patch, dotenvx run -- claude code would decrypt your secrets and then Claude Code would write some of them into .claude/settings.local.json, which could end up in your npm package. Knostic found the file in roughly 1 in 13 npm packages scanned in February. Encrypted storage in git does not protect against this. The decrypted values are what gets persisted.

The fix Anthropic shipped stops that specific write path. It doesn't change the underlying architecture: the agent still inherits the full decrypted environment. Future tools, future state files, future log-capture mechanisms start from that same surface.

The thing most comparisons get wrong

Most discussions of dotenvx focus on the git-safety story ("no plaintext secrets in your repo") and stop there. That story is true and worth caring about. GitGuardian's 2026 State of Secrets Sprawl found AI-assisted commits leak secrets roughly twice as often as the baseline. Encrypted .env files remove one category of that exposure.

But there's a conflation happening: storage security and runtime security are being treated as the same problem. They aren't.

Encrypted storage means: a person who clones your repo and doesn't have the private key can't read your production database URL.

Runtime security means: a process running with access to the decrypted environment can read your production database URL.

When you're running a human developer, runtime security mostly comes for free from normal trust assumptions. You trust the developer. They run commands. The commands see the env. Fine.

When you're running a coding agent, the trust model changes. The agent executes arbitrary tool calls, writes files, runs subprocesses, and (in some configurations) exfiltrates context to external services. The agent is not "you" in any meaningful security sense. Handing it your full decrypted production env because it's a subprocess of your shell is a broader grant than most teams consciously intend.

dotenvx doesn't address this. The encrypted file lives safely in git. The decrypted values still land in the agent's environment.

A checklist for teams evaluating dotenvx

## dotenvx security review

Storage security (dotenvx addresses these):
- [ ] .env files previously committed to git: audit with git log -p | grep -A5 "DATABASE_URL"
- [ ] Plaintext .env files removed from all developer machines after encryption
- [ ] DOTENV_PRIVATE_KEY_* variables rotated after any team member offboarding
- [ ] CI secrets store holds DOTENV_PRIVATE_KEY_*, not the raw values
- [ ] Encrypted files committed but private keys are not (grep repo for DOTENV_PRIVATE_KEY)
- [ ] .env.keys file (generated by dotenvx) added to .gitignore

Runtime security (dotenvx does not address these):
- [ ] Coding agents run with minimum required env, not the full decrypted set
- [ ] DOTENV_PRIVATE_KEY_* not exported in the shell session running the agent
- [ ] Agent state files (.claude/, .cursor/, .aider/) in .gitignore and .npmignore
- [ ] Agent launch script strips production keys when agent runs in dev context
- [ ] Audit log available for which secrets the agent's process accessed

What this means for your stack

dotenvx is a real improvement over unencrypted .env files scattered across machines and Slack threads. If your team still commits plaintext secrets, switching to dotenvx's encrypted-commit model removes a genuine exposure. The git history problem is real; GitGuardian documents it every year.

The gap is at runtime. Once you run dotenvx run -- <agent>, the encrypted-storage story is over. The agent works with live values. If those values include production credentials, the agent has production credentials. Most teams don't consciously decide to grant that. It's an emergent consequence of the full-env decryption model.

The alternative is runtime brokering: instead of decrypting a flat file and handing the whole set to a process, a local broker injects only the values a specific child process needs, at the moment it starts, and revokes access when the process exits. The agent never sees the full secret set. It sees a reference, or a single value, scoped to its task.

hasp is one working implementation of that model. curl -fsSL https://gethasp.com/install.sh | sh, hasp setup, bind a project, and the next agent session receives only what you explicitly grant it rather than whatever your shell happens to hold. Source-available (FCL-1.0), local-first, macOS and Linux, no account.

dotenvx makes git safer. That's worth doing. But the agent-era threat moved downstream of the storage layer, into runtime injection. The tools that track that shift are different from the ones that solved the plaintext-in-git problem.

Sources· cited above, in one place

NEXT STEP~90 seconds

Stop handing the agent your real keys.

hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.

  • Local, encrypted vault — no account, no cloud, no telemetry by default.
  • Brokered run — agent gets a reference, the child process gets the value.
  • Pre-commit + pre-push hooks catch managed values before they ship.
  • Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
→ okvault unlocked · binding ./api
→ okgrant once · pid 88421
→ okagent never read

macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.

Browse all clusters· eight threads, one index