Why .env Files Are a Security Risk — Even After You Delete Them
The file everyone treats casually
Almost every project has one: a .env file, sitting in the project root, holding database
passwords, API keys, maybe a Stripe secret key — in plain text. The standard advice is
“just don’t commit it,” and most teams leave it at that. A .gitignore line, maybe a
reminder in the README, and everyone moves on.
That advice is incomplete, and a piece of real security research shows exactly why.
What happens when a .env file touches git — even once
In 2025, a security researcher working with Truffle Security
scanned what GitHub calls “oops commits” — commits from force-pushes and deleted branches
that developers assumed were gone. GitHub’s public event archive keeps a record of these
as “zero-commit” push events, and it turns out they’re still fully recoverable. Running
automated scans against them, the researcher found thousands of active secrets —
including credentials for MongoDB instances and other production systems, sitting in
.env and config files that someone had “cleaned up” with exactly the kind of force-push
most developers assume erases the problem.
The most striking find: a leaked GitHub Personal Access Token with admin access to Istio, a widely-used open-source project with over 36,000 GitHub stars. Whoever held that token could have pushed code, changed CI/CD pipelines, created releases, or deleted the project’s history entirely — a real path to a supply-chain compromise affecting every project that depends on Istio. The researcher responsibly disclosed it; the token was revoked. The research overall turned into $25,000 in bug bounty payouts — a fairly direct measure of how much real, exploitable risk was sitting in commits people believed they’d deleted.
Why deletion doesn’t work the way people assume
Git was built to preserve history, not erase it. A force-push rewrites which commit a
branch points to — it doesn’t destroy the commit object itself, and GitHub’s own
infrastructure keeps a durable record of push events, including ones that later became
“dangling” (unreachable from any branch, but not gone). Anyone who knows to look — and now,
thanks to open-sourced tooling, anyone who runs a scanner — can find them.
The practical upshot: the moment a real secret is committed, you have to treat it as exposed permanently, regardless of what you do to the branch afterward. Deleting the file, squashing the commit, force-pushing a “fixed” history — none of it un-leaks the secret. Only rotating the actual credential does.
The deeper problem: .env is the wrong shape for this
Beyond the git-history issue, .env files have a structural problem: they’re a single
plaintext file holding everything. There’s no per-secret access control — if a teammate
needs the database password for local development, they get the whole file, Stripe keys
and all. There’s no audit trail of who has which secret. And because it’s just a text
file, it’s exactly as easy to paste into a Slack message or a Jira comment as any other
snippet of text — which is its own separate leak vector entirely.
What to actually do
1. If a secret was ever committed, rotate it. Not “if it’s still in the latest
commit” — if it ever touched a git commit, anywhere, in any branch, at any point.
Assume it’s permanently exposed.
2. Stop using plaintext files as the source of truth for secrets. envseal, an open-source CLI, encrypts each secret individually with age and stores the resulting vault safely in git — the encrypted file is designed to be committed, so there’s no “don’t commit this” landmine to accidentally trip. Each team member gets their own keypair, so granting or revoking access to a secret doesn’t require rotating it, and a compromised laptop doesn’t mean re-issuing every credential your whole team uses.
3. Cover the collaboration-tool gap too. Encrypting your .env handling solves the
git side of this. It doesn’t stop someone from pasting a database connection string into
a Jira comment while debugging, which is a separate, equally real leak path — that’s what
Secret Sentinel scans for directly inside Confluence and
Jira.
Neither tool depends on the other, but together they cover both places secrets actually leak from: the codebase, and the conversations engineers have about it.
Frequently asked questions
If I accidentally commit a .env file and then delete it, am I safe?
No. Git keeps every previous commit's full history, and GitHub archives commits from deleted branches and force-pushes indefinitely as "dangling" commits. Deleting the file from the latest commit does not remove it from history — the only real fix is rotating whatever secret was exposed.
Is "just add .env to .gitignore" enough?
It's necessary but not sufficient. .gitignore only prevents future commits from including the file — it does nothing for a .env that was already committed at any point in the repository's history, including in branches that were later deleted.
What actually happened in the Istio case?
A security researcher found a leaked GitHub Personal Access Token with admin access to the Istio project (36,000+ stars) sitting in a force-pushed "oops commit" that GitHub had archived. The token could have been used to push code, create releases, or delete the project outright.
What's a better alternative to a plaintext .env file?
A per-key encrypted vault that's designed to be committed to git safely, like envseal — so there's no "don't commit this" landmine in the first place, and access can be granted or revoked per person without rotating every secret.