It's a moment most developers dread: you've just pushed a commit and realized your .env file — the one with all your real API keys — went along for the ride.
Having built and shipped personal apps continuously since 2014, I've watched this happen more times than I'd like to admit — to myself and to developers I know. AdMob keys, Stripe production secrets, Anthropic API keys. The more services your personal project integrates, the higher the risk of a .env slipping through.
This guide walks you through what to do after it's already happened, in the order that actually matters. Done methodically, you can close the window within 30 minutes.
First 5 Minutes: Rotate Your Keys Before Anything Else
Rewriting git history doesn't undo the past. Between the moment of the push and now, automated scanners — and potentially bad actors — may have already copied those keys. Rotation is the only thing that makes the leaked credentials useless.
Anthropic API key:
- Open console.anthropic.com → API Keys
- Find the exposed key and click Revoke
- Generate a new key and update your local
.env
Google AdMob / Firebase:
- Go to console.firebase.google.com → Project Settings → Service Accounts or API Keys
- Disable the exposed key and create a replacement
Stripe:
- Open dashboard.stripe.com → Developers → API keys
- For production keys, use Roll to instantly invalidate and replace in one step
Finish rotation before you touch git. A rotated key is worthless to anyone who grabbed it.
Assess the Damage
Once your keys are safe, understand the scope of the exposure:
# Find commits that included .env
git log --all --full-history -- .env
# Search for the key pattern itself (replace with your key prefix)
git log -S "sk-ant-" --all --oneline
git log -S "sk_live_" --all --onelineIf the repo is on GitHub, check Settings → Secret scanning → Alerts. GitHub automatically detects patterns for major providers (Anthropic, AWS, GCP, Stripe, OpenAI, and more) and will alert you if it found anything. An open alert means GitHub caught it; close the alert once you've completed the steps below.
Remove the Keys from Git History
Option 1: git-filter-repo (Recommended)
# Install git-filter-repo
pip install git-filter-repo --break-system-packages
# or on macOS
brew install git-filter-repo
# Remove .env from the entire history
cd /path/to/your-repo
git filter-repo --path .env --invert-paths --force
# Force-push the rewritten history
git remote add origin https://github.com/your-user/your-repo.git
git push origin --force --all
git push origin --force --tags--invert-paths keeps everything except the specified path. After this, .env will not exist in any commit in your entire history.
Option 2: git filter-branch (Older Environments)
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
git push origin --force --allAsking Claude Code to Do It
You can delegate this to Claude Code with a clear prompt:
Remove the .env file from the entire git history using git filter-repo.
Use --invert-paths to exclude .env from all commits.
Then force-push to origin at https://github.com/your-user/your-repo.git.
Do not ask for confirmation — proceed immediately.
If Claude Code pauses and asks for confirmation before rewriting history, respond with "Please proceed without confirmation." It will continue.
Notify Collaborators (Team Projects Only)
Force-pushing rewrites shared history, which means anyone else's local clone will be out of sync. After the push, ask every collaborator to run:
git fetch origin
git reset --hard origin/mainFor solo projects — which covers most indie developers — skip this step entirely.
Add .env to .gitignore
Prevent the next incident by confirming your ignore rules are correct:
# Check if .env is already ignored
cat .gitignore | grep -E "^\.env$"
# Add it if missing
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.*.local" >> .gitignore
git add .gitignore
git commit -m "chore: add .env to gitignore"
git push origin mainIf .env was already in .gitignore but still got committed, it was likely added to git tracking before the ignore rule existed. Remove it from tracking without deleting the file:
git rm --cached .env
git commit -m "chore: untrack .env"Close the GitHub Secret Scanning Alert
If GitHub opened an alert when it detected the key pattern, go to Security → Secret scanning → Alerts, open the specific alert, and mark it as Resolved (revoked). GitHub will verify that the secret is no longer active and close the alert.
The detection speed surprised me the first time I saw it in action. When a test Stripe key ended up in a commit for one of my wallpaper app's backend repos, Stripe's own monitoring flagged it before GitHub's email even arrived. These systems are fast — which is exactly why key rotation must come first.
Prevention: Build the Safety Net
Use .env.example for documentation
Commit a .env.example with placeholder values instead of the real file:
# .env.example — safe to commit
ANTHROPIC_API_KEY=your_api_key_here
STRIPE_SECRET_KEY=your_stripe_key_here
ADMOB_APP_ID=your_admob_id_here
# .env — never commit this
ANTHROPIC_API_KEY=sk-ant-api03-xxxxx
STRIPE_SECRET_KEY=sk_live_xxxxx
ADMOB_APP_ID=ca-app-pub-xxxxxAdd a pre-commit hook
A simple shell hook will catch .env before it ever reaches a commit:
# .git/hooks/pre-commit (make it executable: chmod +x)
#!/bin/bash
if git diff --cached --name-only | grep -q "^\.env$"; then
echo "❌ .env is staged for commit. Run: git rm --cached .env"
exit 1
fiTell Claude Code in CLAUDE.md
Adding a note in your project's CLAUDE.md helps Claude Code make the right call when it encounters .env during automated tasks:
## Security Rules
- Never add .env to git staging or commits
- Pass API keys through environment variables, not hardcoded values
- Before running `git add .`, confirm .env is not includedThe order matters: rotate first, rewrite history second, prevent third. Jumping straight to the git cleanup while the old key is still active leaves you exposed during the time it takes.
If you're reading this before it's happened to you, setting up the pre-commit hook right now takes about two minutes and will save you a much longer conversation with yourself later.