Accidental secret commits are one of the most common and most dangerous security mistakes in modern development.
A single leaked API key or connection string can give attackers persistent access long after you’ve deleted the file. The real challenge isn’t fixing the code — it’s cleaning the history.
This post walks through how to safely and permanently remove sensitive data from your Git history.
Why It’s Dangerous
When secrets — such as API keys, SSH private keys, OAuth tokens, or database credentials — are committed to a public or even private Git repository, the risks are serious:
- Security breaches: Attackers can replay commit history to extract secrets.
- Permanent exposure: Even if the file is deleted, it remains in Git history and GitHub forks/caches.
- Automation risk: Compromised tokens can trigger, pipelines.
Deleting the file isn’t enough. You must rewrite Git history to remove it completely.
Step-by-Step Guide: Remove Secrets Using git filter-repo
Warning: History rewriting is destructive.
Always create a backup or clone before proceeding.
Coordinate with your team as everyone will need to re clone the repository after cleanup.
Step 1. Create a Safety Backup
Before touching your Git history, make a full backup. This gives you a restore point if anything goes wrong.
git clone https://github.com/org/repo.git repo-backup
Or download a ZIP snapshot from GitHub’s “Code → Download ZIP” menu.
Tip: Don’t skip this step. Once you rewrite history, the old commit hashes are gone forever.
Step 2. Prepare a Clean Mirror for Rewriting
You’ll use a mirror clone — a minimal, metadata-rich copy of your repository — ideal for surgical operations.
git clone --mirror https://github.com/org/repo.git repo-clean.git
cd repo-clean.git
Step 3. Install git-filter-repo
git-filter-repo is the modern, fast, and officially recommended tool for rewriting Git history (it replaces the older git filter-branch).
Install:
brew install git-filter-repo # macOS
pip install git-filter-repo # Cross-platform
Step 4. Use git-filter-repo to Remove Secrets
Now, remove the sensitive file from every commit in your repository’s history.
git filter-repo --path "config/secrets.json" --invert-paths
If you’ve exposed multiple files, include each path:
git filter-repo --invert-paths --path ".env" --path "settings.py"
Why this works:
The --invert-paths flag deletes the file (or files) from every commit, ensuring no historical trace remains.
Step 5. Verify Remote Configuration
Check your repository’s remote URL — sometimes it’s lost during history rewriting.
git remote -v
If missing, re-add it:
git remote set-url origin https://github.com/org/repo.git
Step 6. Push the Clean History Back
Replace your remote history with the cleaned version.
git push --force --all
git push --force --tags
All existing collaborators will need to re-sync their clones.
Communicate clearly before pushing.
Step 7. Refresh Your Local Clones
Every developer using this repo must re-sync after history rewriting.
cd repo git fetch origin git reset --hard origin/main
Tip: Never merge after a forced history rewrite — always hard reset to the updated branch.
Step 8. Verify That Secrets Are Gone
Confirm that no old commits still contain the sensitive file:
git log --all -- config/secrets.json
If it returns nothing, you’re clear. You can now safely delete the mirror repo:
rm -rf repo-clean.git
Final Thoughts and Key Takeaways
Rewriting Git history may feel risky, but git filter-repo makes it clean, fast, and reliable. It’s the simplest way to erase secrets for good and prevent long-term security exposure. That said — once a secret’s been committed, assume it’s compromised. Rotate it right away and revoke any old tokens or credentials.
- Rotate exposed keys immediately.
- Add. env and config files to .gitignore.
- Use secret scanning to catch issues early.
- Notify your team before force-pushing.