GitHub just sent you a secret scanning alert. Or maybe you noticed your AWS bill jumped overnight. Either way, you've got credentials in your git history and you need them gone.
Here's the complete guide to removing secrets from git history — and how to make sure it never happens again.
Why deleting the file isn't enough
This is the most common mistake:
git rm .env
git commit -m "remove env file"
git push
That removes the file from the current state of the repo. But anyone who clones it can still run:
git log -p | grep AWS_ACCESS_KEY
And see your secret in full. Every commit that ever contained your .env file is permanently stored in git history — until you rewrite that history.
Step 1: Rotate your credentials immediately
Before doing anything else — rotate the exposed credentials. Assume they're already compromised. AWS keys, Stripe keys, GitHub tokens — rotate them all before proceeding.
Git history rewrites take time. The keys might already have been scraped.
Step 2: Check what's in your history
# Check if any .env files were ever committed
git log --all --full-history -- "*.env" ".env*"
# Search for specific patterns in history
git log -p --all | grep -E "AKIA[0-9A-Z]{16}" # AWS keys
git log -p --all | grep -E "sk_(live|test)_" # Stripe keys
Or use GitCleanse to do it automatically:
npm install -g gitcleanse
gitcleanse scan # scans full history for secrets
gitcleanse check # quick .env check
Step 3: Remove from history
Option A: git-filter-repo (recommended)
Install it first:
pip install git-filter-repo
Remove specific files:
git filter-repo --path .env --invert-paths --force
git filter-repo --path .env.production --invert-paths --force
Option B: BFG Repo Cleaner
# Install BFG
brew install bfg # macOS
# Remove files
bfg --delete-files .env
git reflog expire --expire=now --all && git gc --prune=now --aggressive
Option C: git filter-branch (always available, slower)
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch .env .env.production' \
--prune-empty --tag-name-filter cat -- --all
git reflog expire --expire=now --all
git gc --prune=now --aggressive
Or generate these commands automatically:
gitcleanse clean --auto --output cleanup.sh
# Review cleanup.sh, then:
bash cleanup.sh
Step 4: Force push to remote
⚠️ Coordinate with your team first. This rewrites history — everyone will need to re-clone.
git push origin --force --all
git push origin --force --tags
Step 5: Ask GitHub/GitLab to purge caches
Even after rewriting history, hosted services may cache old objects. Contact GitHub support or go to Settings ‚Üí Danger Zone and request a cache purge.
Step 6: Prevent it from happening again
Add to .gitignore:
.env
.env.*
!.env.example
Use EnvGuard as a pre-commit hook:
npm install -g envguard
# .git/hooks/pre-commit:
envguard audit --strict
It'll catch secrets before they're ever committed.
TL;DR
- Rotate all exposed credentials immediately
- Use
git filter-repoto remove the file from history - Force push to remote
- Ask GitHub to purge caches
- Add
.envto.gitignoreand use a pre-commit hook
The whole process takes about 15 minutes once you know the steps. The pain is in not knowing them at 2am when the alert hits.
Top comments (0)