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:

  1. Rotate/Revoke the leaked secret - Consider it compromised and generate new credentials
  2. Backup your repository if it contains important work
  3. 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 main

Warning: 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 --all

Option 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 --all

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 --all

Create 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 main

Post-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 --tags

3. Team Coordination

Send this message to your team:

“I’ve cleaned our Git repository to remove leaked secrets. Please:

  1. Back up any local work
  2. Delete your local repository: rm -rf project-name
  3. Fresh clone: git clone <repository-url>
  4. 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-hooks

Git 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 main

Large Repository Takes Too Long

# Use git-filter-repo instead of filter-branch
# It's significantly faster for large repositories

Collaborators Still Have Old History

# They must delete and re-clone
rm -rf repository-name
git clone <repository-url>

Summary

MethodUse CaseProsCons
Complete ResetSmall repos, fresh startSimple, guaranteedLoses all history
Selective RewritePreserve historyKeeps commitsComplex, time-consuming
Reset to PointRecent leakQuick, preserves old historyLoses 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.