Git Hygiene
Why .env Files Keep Ending Up in Git History
A .gitignore entry added after the first commit does not undo anything. How .env files get committed, and how to clean up.
5 min.
Nobody commits a .env file on purpose. It happens because the file is created before anyone thinks about ignoring it, and Git is very good at remembering things you told it once. Months later a scanner flags the repository, and the team discovers that a rule they added long ago never applied to the file they added it for.
The order of operations matters
Almost every .env leak follows the same sequence. The project starts, a developer creates .env to hold local credentials, and the first few commits sweep up everything in the working directory. Later someone notices the file in a diff, adds it to .gitignore, and assumes the problem is solved.
It is not, because .gitignore only decides whether Git should start tracking a new path. A file already in the index keeps being staged, committed, and pushed, quietly, on every change.
Why the ignore rule feels like a fix
The rule does change one visible thing: the file stops appearing in the untracked section of git status. That is enough to convince most people the leak is closed, and it is the single most common reason a credential stays live for a year.
The reliable check is not the ignore file, it is the index. If git ls-files lists the path, the file is tracked no matter what .gitignore says.
Deleting the file is not enough either
Removing .env in a new commit clears it from the working tree and from the latest snapshot. It does nothing to the commits behind it. Every clone, every fork, and every stale CI cache still contains the original values, and history is exactly what an automated scraper walks.
If the repository was ever public, assume the credentials were read. Scrapers monitor the public event stream and act on new secrets within minutes, which is faster than most teams notice the commit.
What to do, in order
Rotate every credential the file contained. This is the only step that actually stops the exposure.
Untrack the file so the next commit does not re-add it, then confirm with the index rather than the ignore rule.
Review the provider audit logs for the window between the first commit and the rotation.
Rewrite history only if the repository is private and every collaborator can re-clone in a coordinated way.
Commit an example file with placeholder values so the next developer has somewhere safe to start.
Rewriting history is cleanup. Rotation is the fix. Doing them in the wrong order just makes the leak harder to find.
Why history rewriting is the last resort
Rewriting changes every commit hash after the edit point. Open branches need rebasing, existing clones break, review links and CI caches go stale, and forks keep the original objects anyway. It is worth doing for a private repository with a small team, and rarely worth the disruption for a public one where the values are already burned.
What to tell the team
Be specific: which credentials were rotated, when the old ones were revoked, and what the audit log showed. A short written note prevents the same key from being pasted back into a local file next week.
A worked example, start to finish
A team finds DATABASE_URL and STRIPE_SECRET_KEY in a .env committed fourteen months ago. The repository is private, with nine contributors and two forks inside the organisation.
They rotate the Stripe key first, because a payment credential has the widest consequences, then the database password during a maintenance window that evening. Both provider audit logs show no unfamiliar access, which is recorded in the ticket rather than assumed. The file is untracked the same afternoon, and an example file with placeholder keys replaces it so nobody recreates the original.
History rewriting is scheduled for the following week, coordinated in advance, and treated as hygiene rather than remediation. Total exposure closed in under a day; total disruption, one evening deploy.
What made it manageable
Nothing in that sequence required heroics. It worked because the team knew which credentials the file contained, where each one was consumed, and which provider console to open for each rotation.
Prevention that survives deadlines
Process documents do not stop this. Automation does. A pre-commit hook that refuses files matching known secret shapes, a scan on every push, and a single secret store that the whole team uses will catch the pattern while it is still cheap.
The strongest signal that prevention is working is boring: developers stop needing to think about it, because the check fails before the commit ever reaches a shared branch.
KAIKI flags .env exposures as high severity, points at the exact commit and line, and lists the credential types involved, so you know precisely what to rotate before you touch history.
Ship With Confidence
Zero config, 50+ secret patterns, AI-powered analysis. Start scanning in seconds.