Git Secret Cleanup Guide: Removing Leaked Secrets from Repository History
When secrets (API keys, passwords, tokens, etc.) are accidentally committed to a Git repository, simply removing them in a new commit is not enough. The secrets remain in your Git history and can be accessed by anyone with repository access. This guide covers three methods to completely remove secrets from your Git history.
⚠️ Critical First Steps (Do This Immediately)
Before cleaning your Git history:
- Rotate/Revoke the leaked secret - Consider it compromised and generate new credentials
- Backup your repository if it contains important work
- Coordinate with team members - they’ll need to re-clone after cleanup
Method 1: Complete Repository Reset (Nuclear Option)
Best for: Small repositories, or when you want to start completely fresh.
Pros: Simple, guaranteed to work, removes everything Cons: Loses all commit history and metadata
# Step 1: Remove all Git history
rm -rf .git
# Step 2: Initialize new repository
git init
# Step 3: Add all current files (make sure secrets are removed first!)
git add .
git commit -m "Initial commit - cleaned repository"
# Step 4: Connect to remote and force push
git remote add origin <your-remote-url>
git push -f origin mainWarning: This permanently deletes ALL commit history, branches, and tags.
Method 2: Selective History Rewriting
Best for: When you want to preserve commit history but remove specific files or content.
Option A: Remove Specific Files
# Remove a specific file from entire history
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch path/to/secret-file.txt' \
--prune-empty --tag-name-filter cat -- --all
# Clean up references
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# Force push to remote
git push --force-with-lease origin --allOption B: Remove Files by Pattern
# Remove all .env files from history
git filter-branch --force --tree-filter \
'find . -name "*.env" -delete' \
--prune-empty --tag-name-filter cat -- --all
# Clean up
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# Force push
git push --force-with-lease origin --allOption C: Using git-filter-repo (Recommended)
git-filter-repo is a modern, faster alternative to git filter-branch.
# Install git-filter-repo
pip install git-filter-repo
# Remove specific file
git filter-repo --path path/to/secret-file.txt --invert-paths
# Remove files by pattern
git filter-repo --path-glob '*.env' --invert-paths
# Remove content containing specific text
git filter-repo --replace-text expressions.txt
# Force push
git push --force-with-lease origin --allCreate expressions.txt for text replacement:
API_KEY=***REMOVED***
PASSWORD=***REMOVED***
SECRET_TOKEN=***REMOVED***
Method 3: Reset to Specific Clean Point
Best for: When you know exactly when the secret was introduced and want to preserve history before that point.
# Step 1: Find the last clean commit
git log --oneline --graph
# Step 2: Reset to clean commit (replace with actual hash)
git reset --hard <commit-hash-before-secret>
# Step 3: Force push to remote
git push --force-with-lease origin main
# Alternative: Create new branch from clean point
git checkout <clean-commit-hash>
git checkout -b clean-main
git branch -D main # Delete contaminated main
git branch -m main # Rename clean-main to main
git push --force-with-lease origin mainPost-Cleanup Steps
1. Verify Cleanup
# Search for any remaining secrets
git log --all --full-history -- path/to/secret-file.txt
git grep -r "API_KEY" $(git rev-list --all)2. Update Remote Repository
# Force push all branches and tags
git push --force-with-lease origin --all
git push --force-with-lease origin --tags3. Team Coordination
Send this message to your team:
“I’ve cleaned our Git repository to remove leaked secrets. Please:
- Back up any local work
- Delete your local repository:
rm -rf project-name- Fresh clone:
git clone <repository-url>- Update any local environment files with new credentials”
4. Additional Security Measures
- Check if your repository has forks that might contain the secret
- Review CI/CD logs that might have captured the secret
- Scan other repositories for similar issues
- Consider implementing pre-commit hooks to prevent future leaks
Prevention for the Future
Pre-commit Hooks
# Install pre-commit
pip install pre-commit
# Add to .pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
#Install precommit hooks
pre-commit install --install-hooksGit Ignore Patterns
Add to .gitignore:
# Environment files
.env
.env.local
.env.*.local
# Configuration files
config.json
secrets.yml
*.pem
*.key
# IDE files
.vscode/settings.json
.idea/
Environment Variable Usage
# Instead of hardcoding secrets
API_KEY="YOUR_API_KEY_HERE"
# Use environment variables
API_KEY="${API_KEY}"Troubleshooting
”Cannot force push” Error
# If protected branch, temporarily disable protection or use
git push --force origin mainLarge Repository Takes Too Long
# Use git-filter-repo instead of filter-branch
# It's significantly faster for large repositoriesCollaborators Still Have Old History
# They must delete and re-clone
rm -rf repository-name
git clone <repository-url>Summary
| Method | Use Case | Pros | Cons |
|---|---|---|---|
| Complete Reset | Small repos, fresh start | Simple, guaranteed | Loses all history |
| Selective Rewrite | Preserve history | Keeps commits | Complex, time-consuming |
| Reset to Point | Recent leak | Quick, preserves old history | Loses recent work |
Remember: The most important step is rotating your leaked credentials immediately, regardless of which cleanup method you choose. Git history cleanup is about preventing future access to old credentials, but you must assume the leaked secret has already been compromised.