Deleting a committed .env file doesn't remove it from git history
By Seaworthy · 2 September 2026
Photo by Gilang Cipta Permana on Unsplash
At some point in nearly every project's history, someone runs git add . before .gitignore has a line for .env, and a file full of API keys and database URLs gets committed. Usually it's caught fast: a teammate notices, a follow-up commit deletes the file, .env gets added to .gitignore, and the branch moves on. The keys are gone from the working tree, so the problem looks solved.
Deleting the file doesn't delete the commit
git rm .env
git commit -m "remove env file"
This removes .env from the current state of the repository. It does nothing to the earlier commit where the file was added, which is still sitting in the repository's history with the full contents intact. Anyone with clone access, right now or at any point in the future, can run:
git log --all --full-history -- .env
git show <commit-hash>:.env
and read exactly what was in it, including whatever secrets were live at the time. If the repository is public, that's anyone on the internet. If it's private, it's every current and former collaborator, every CI system with clone access, and every fork made before the cleanup commit.
The only real fix is rotation, not history
Once a secret has been committed, even briefly, treat it as compromised and rotate it: generate a new API key, a new database password, a new signing secret, and revoke the old one at the provider. This is true even if you're fairly confident no one saw it. "Fairly confident" isn't a security property, and rotation is cheap compared to the alternative.
Rewriting git history to strip the file out, with git filter-repo or BFG Repo-Cleaner, is worth doing for hygiene, but it doesn't undo an exposure that already happened. Anyone who cloned or fetched before the rewrite still has the old commits locally, and a force-push doesn't reach into their machine and remove them. History rewriting is cleanup, not a substitute for rotating the credential.
Stop it from happening again with a pre-commit check
# .gitignore, from the start of the project
.env
.env.local
.env.*.local
A .gitignore entry only protects files that were never tracked. If .env was committed even once early on, adding it to .gitignore later does not untrack it automatically, git rm --cached .env is still required, or the file keeps getting picked up by future commits despite the ignore rule.
A pre-commit hook that scans staged changes for high-entropy strings and known key formats catches this before it reaches a commit at all, which is a better position to be in than catching it in a code review after the branch is already pushed:
npx --yes gitleaks protect --staged
Check what's actually in your history right now
If you've never checked, it's worth running a history-wide secret scan once, rather than assuming your project is fine because you don't remember committing anything sensitive:
npx --yes gitleaks detect --source . --report-path gitleaks-report.json
Old commits from before anyone on the current team joined are a common source of surprises here.
Seaworthy scans committed history, not just the working tree, for exposed secrets.
This article was generated by AI and summarises publicly available sources.
Seaworthy scans your repo for the issues covered in articles like this one.
Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.