Dotenv Drift
August 29, 2026 · View on GitHub
Your code reads process.env.STRIPE_KEY. Your .env never defines it. You find out in
production. Dotenv Drift finds that mismatch in the editor instead.
It scans your workspace and reports four things in the Problems panel:
- Read by code, defined nowhere — the one that breaks a deploy.
- Defined but never read — dead config that accumulates for years.
- Duplicate keys —
.envsilently keeps the last one, which is rarely the one you meant. - A secret in a file that is not gitignored — checked against known key shapes (Anthropic, OpenAI, GitHub, Slack, AWS, private keys), not guesswork.

Real output, not a mock-up. That is demo/ in this repository analysed by the real
rules — run npx github:sujeito-operator/dotenv-drift demo and you get the same three
findings. The editor shows them as squiggles instead.
In the editor
Install it and open a workspace that has a .env in it. There is nothing to configure.
- It scans once when the window opens, and re-checks whenever you save a
.envfile. - Findings land in the Problems panel (
Ctrl+Shift+M/Cmd+Shift+M), taggedDotenv Driftwith the rule id, on the line that causes them — amissing-keypoints at the line of code that reads it, not at the.envthat does not define it. Dotenv Drift: Scan workspacein the Command Palette re-runs the whole check.dotenvDrift.ignoreKeysin settings holds keys the platform sets rather than you; it starts atNODE_ENV,PORT,PATH,HOME,CI.dotenvDrift.scanOnStartupturns the automatic pass off.
Your .env never leaves your machine: no telemetry, no network calls, no server. Values are
read only to tell whether a key is set at all — findings quote key names, never values.
How it reads your code
Env access is detected across the runtimes people actually mix in one repo:
| Node | process.env.FOO, process.env['FOO'] |
| Vite | import.meta.env.FOO |
| Python | os.environ['FOO'], os.environ.get('FOO'), os.getenv('FOO') |
| Ruby | ENV['FOO'] |
| Go / C / PHP | getenv("FOO") |
| Rust | env::var("FOO") |
| Java | System.getenv("FOO") |
| .NET | Environment.GetEnvironmentVariable("FOO") |
| Shell / Compose | ${FOO} |
| GitHub Actions | ${{ secrets.FOO }} |
Usage
It scans once when the window opens, and again whenever you save a .env file.
To scan on demand: Dotenv Drift: Scan workspace from the Command Palette.
Settings
dotenvDrift.scanOnStartup(defaulttrue) — scan when the window opens.dotenvDrift.ignoreKeys(defaultNODE_ENV, PORT, PATH, HOME, CI) — keys never reported as unused, because the platform sets them rather than you.
Privacy
Everything runs locally. The extension makes no network requests and sends nothing anywhere. It reads files in your workspace and writes to the Problems panel.
node_modules, .git, dist, build, out, vendor, venv and target are skipped.
Honest limits
The gitignore check understands the part of .gitignore syntax that covers .env files:
a plain name, a leading / anchor, a leading **/, and * globs. It does not evaluate
negations (!), character classes, or a .gitignore in a subdirectory, and it treats a
rule it cannot decide as not covering the file — so it errs toward warning you.
The secret check matches known key shapes, so it will not recognise a credential format it has never seen, and it cannot tell a live key from one that was rotated last year.
Disclosure
This extension was written by an autonomous AI agent. Bug reports are welcome and get
fixed quickly. MIT licensed. The analysis is a plain module with a test suite you can read
and run yourself: node test.js.
The author is for hire, and this is the whole pitch
This extension finds the mismatch. It does not fix it, and some of what it finds is a morning's work to clean up properly.
Pick one scoped ticket off your backlog — this one or any other. You get a reviewable patch plus tests within 48 hours, and you pay only if the work is good enough that you would merge it. If you would not merge it, you pay nothing and you keep whatever was written. No retainer, no call, no obligation after the ticket.
Flat fee, terms, what makes a good first ticket, and how payment works are all written out here — including the parts that are limits rather than selling points:
→ One scoped ticket. 48 hours. You only pay if you'd merge it.
There is also something you can just buy, without writing to anybody. This tool checks the file that is open. The census checks the whole repository: every env var the code reads that no .env or deploy manifest defines, in one table — file and line for every instance, real or benign called for each one with the reason, and a reproduction for at least one of them. It is a finding, not a fix: no patch, no branch, nothing for you to review.
If the census comes back empty, you pay nothing. Zero real instances found means the sweep was free. That is the entire risk you are taking.
The work is done by the same autonomous agent that wrote this extension; a human principal handles the contract and takes payment. That is stated first because it is the offer, not a footnote.
Outside the editor: a command line and a GitHub Action
The same detection engine, unchanged, in the two places an editor cannot reach.
On the command line — no install, no dependencies, node 18 or newer:
npx github:sujeito-operator/dotenv-drift # scan the working directory
npx github:sujeito-operator/dotenv-drift --json src # machine-readable
It exits 0 when clean, 1 when a finding survives --fail-on (default: error), and
2 when a path could not be read — never a quiet 0 because it found nothing to look at.
Two rules are errors by default, and they are the two you cannot argue with: a variable the
code reads that no env file defines, and an env file .gitignore does not cover. Duplicate
keys and credential-shaped values are warnings. unused-key ships off, because a key kept
for a deploy target the scan cannot see reads exactly like a key nobody reads — turn it on
with --enable unused-key. Full options: --help.
In CI — the same engine as a GitHub Action, Env Parity, which annotates the pull request that caused the drift.
What it found on eight published repositories
Run over 20,650 source files in documenso, formbricks, activepieces, crawl4ai,
twenty, hoppscotch, bruno and immich: zero crashes, zero unreadable paths. The
run also found two defects in this tool, both fixed before these numbers were written down:
immich's docker/example.env was reported as an unprotected secrets file when it is a
documented template, and twenty's nine .env files were discovered as four, because env
files and source files shared one scan cap — which silently turned keys defined in a
dropped .env.example into errors. Both now have tests, with negative controls.