How .env files end up in Git and what to do about it
Par Seaworthy · 14 juin 2026
Photo by Salah Ait Mokhtar on Unsplash
How .env files end up in Git and what to do about it
The most common secret leak I see is not a sophisticated attack. It is not a supply chain compromise or a stolen SSH key. It is a developer who ran a scaffold command, followed the instructions line by line, and committed a .env file before anyone thought to add it to .gitignore.
I want to be specific about why this keeps happening, because "be careful with secrets" is useless advice. The actual mechanism matters.
The AI scaffold problem
When you ask an LLM to scaffold a new project, it will often produce a sequence of commands like this:
cp .env.example .env
# Edit .env with your values
npm install
git add .
git commit -m "initial setup"
Notice what is missing? The step to add .env to .gitignore never appears, or it appears as an afterthought after the git add . command has already been issued. The instructions are technically complete in the sense that the app will run, but they leave credentials staged and ready to commit.
This is not hypothetical. Secret scanning tools routinely surface credentials in repositories months after the fact. The rotation window, the period when you can act before someone else does, is often long closed by the time the leak is discovered.
Why git add . is the real culprit
The combination of git add . and a missing .gitignore entry is what actually lands secrets in history. This is worth understanding at the file level.
A fresh repository with no .gitignore will happily track everything:
my-project/
.env ← contains DATABASE_URL, API_KEY, etc.
.env.example ← safe, just placeholders
src/
package.json
Running git add . stages every file in the working tree, including .env. Once you commit, those credentials are in the object database. Deleting the file in a subsequent commit does not remove the secret from history: anyone who clones the repo and runs git log -p can read the original file content.
The correct setup looks like this:
# Create .gitignore BEFORE adding files
echo ".env" >> .gitignore
cp .env.example .env
# Now edit .env with real values
git add .gitignore .env.example # explicit, not git add .
git commit -m "initial setup"
Two differences: .gitignore is created first, and git add names specific files rather than a glob. Neither of these steps appears reliably in AI-generated scaffolding output.
What to do if it is already in history
If you have already committed a .env file, deleting it and adding a .gitignore entry is not sufficient. The credentials are still in history.
The options, none of them pleasant, are:
- Rotate the credentials immediately. This is the only reliable fix. History rewriting with
git filter-repocan remove the file from commits, but if the repository has been pushed to a remote and cloned by anyone, you cannot guarantee all copies have been scrubbed. - Rewrite history with
git filter-repoand force-push, then contact GitHub or GitLab to purge cached views. This is disruptive if the repository has collaborators. - Treat the credentials as permanently compromised and rotate them regardless of whether you rewrite history.
Rotation is the action that actually closes the risk. History rewriting is optional cleanup.
# Remove .env from all commits (requires git-filter-repo)
git filter-repo --path .env --invert-paths
# Force push all branches (destructive, coordinate with your team first)
git push origin --force --all
Warning: force-pushing rewrites the public history of the repository. Anyone with a local clone will need to re-clone or manually reset their branches.
The .gitignore entry you actually need
A lot of developers add .env to .gitignore and then wonder why .env.local or .env.production still shows up. The glob patterns are not as forgiving as you might expect:
# This only excludes .env exactly
.env
# This excludes .env and any .env.* variant
.env*
# More selective: excludes variants but keeps .env.example tracked
.env*
!.env.example
The ! negation lets you commit the example file while blocking all populated variants. Check your pattern with git status before you commit anything.
Why this keeps happening
The issue is not that developers are careless. It is that the tooling defaults make the bad path easier than the safe one. git add . is shorter to type than git add -p. AI-generated instructions optimise for getting the app running, not for operational safety. The .gitignore step feels like housekeeping, so it gets deferred or skipped.
The fix is to treat .gitignore as the first file you create in any new project, before you copy in any secrets, and to never use git add . in a directory that contains credentials.
Seaworthy flags committed .env files and missing .gitignore entries automatically as part of its data exposure checks.
Cet article a été généré par IA et résume des sources publiquement disponibles.
Seaworthy analyse votre dépôt pour détecter les problèmes abordés dans des articles comme celui-ci.
Failles de sécurité, secrets exposés et mauvaises configurations détectés avant le déploiement. Gratuit, aucun compte requis.