Git Security

How to Remove API Keys
From Git History

Deleting the file isn't enough — the key still lives in every previous commit. Here's the complete playbook.

8 min read·Updated Mar 2026

Do this first — before reading anything else

Revoke or rotate the exposed key immediately from your provider's dashboard. Assume it has already been scraped — automated bots scan GitHub commits in real time. History cleanup comes second.

How to remove API keys from Git history is something most developers only search after the panic has already set in. You committed a .env file, pushed to GitHub, and then realised what you'd done. The instinct is to delete the file and commit the deletion. That doesn't work — the key still exists in every previous commit in your repository's history.

Why Deleting the File Isn't Enough

Git is designed to never lose data. When you commit a deletion, Git records that the file is gone in the new commit — but all previous commits still contain it. Anyone can run git log, find the commit where the file was added, check it out, and read your secret.

If your repository is public, it's worse. GitHub caches repository content for performance. Even after you delete the file, cached views of the commit may remain accessible. And within seconds of a push, automated scanners are crawling public commits looking for AWS keys, Stripe secrets, OpenAI tokens and hundreds of other patterns.

// What most developers do (wrong)
git rm .env
git commit -m "remove .env"
git push
// The key is still in every commit before this one ↑
// What you actually need to do
git filter-repo --replace-text secrets.txt
git push --force --all

Step 1 — Revoke the Key Immediately

Before touching Git, go to your provider's dashboard and revoke or rotate the exposed credential. This is the only action that actually stops the damage. History cleanup stops future exposure — it doesn't undo what's already happened.

AWS
IAM → Security Credentials → Deactivate key → Create new
Stripe
Dashboard → Developers → API Keys → Roll key
OpenAI
Platform → API Keys → Delete → Create new
GitHub PAT
Settings → Developer Settings → Personal Access Tokens → Delete
Google
Console → APIs & Services → Credentials → Delete
Twilio
Console → Account → API Keys → Revoke

Step 2 — Choose Your Tool

There are two main options. git-filter-repo is the modern standard — recommended by GitHub, faster than the old git filter-branch, and requires only Python. BFG Repo-Cleaner is simpler for basic cases but requires Java.

Tool Requires Speed Best For
git-filter-repo Python 3 Fastest Recommended for all cases
BFG Repo-Cleaner Java 11+ Very fast Simple file deletion or string replacement
git filter-branch Nothing extra Very slow Avoid — deprecated and error-prone

Method A — git-filter-repo (Recommended)

Install

pip install git-filter-repo

Option 1: Replace the secret string everywhere in history

Create a text file listing what to replace:

# secrets.txt — one replacement per line
sk_live_abc123xyz==>REDACTED
AKIA1234567890ABCDEF==>REDACTED
# Clone a fresh mirror first
git clone --mirror https://github.com/you/repo.git repo-mirror.git
cd repo-mirror.git

# Run the replacement
git filter-repo --replace-text ../secrets.txt

# Push cleaned history
git push --force --all
git push --force --tags

Option 2: Remove an entire file from history

git clone --mirror https://github.com/you/repo.git repo-mirror.git
cd repo-mirror.git

# Remove .env from all commits
git filter-repo --path .env --invert-paths

git push --force --all
git push --force --tags

Method B — BFG Repo-Cleaner

Download the JAR from rtyley.github.io/bfg-repo-cleaner. Requires Java 11+.

# Replace secrets listed in a file
java -jar bfg.jar --replace-text secrets.txt repo-mirror.git

# Or delete a specific file from all history
java -jar bfg.jar --delete-files .env repo-mirror.git

# Clean up and push
cd repo-mirror.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force --all

BFG doesn't clean the HEAD commit by default

BFG protects your latest commit. Make sure your current HEAD doesn't contain the secret before running BFG — remove it manually first, commit the clean version, then run BFG on the history.

Step 3 — Tell Your Team

Force-pushing rewrites history. Every team member's local clone now has diverged history. They must delete their local copy and re-clone:

# Each team member needs to run this
git fetch --all
git reset --hard origin/main
# Or just: delete local clone and git clone again

GitHub caches old commits

Even after force-pushing, GitHub may serve cached views of old commits. Contact GitHub Support and request they flush cached views of your repository. This is especially important for public repos where the key may have been indexed by search engines.

Step 4 — Stop It Happening Again

1
Add secrets to .gitignore
Add .env, *.pem, config/secrets.* to your .gitignore immediately. Add a .env.example with placeholder values for documentation.
2
Use a pre-commit hook
Install detect-secrets or gitleaks as a pre-commit hook. It scans staged files for API key patterns before the commit is made.
3
Enable GitHub Secret Scanning
Go to your repo Settings → Code security → Secret scanning. Enable push protection — GitHub will block pushes containing known secret patterns from 200+ providers including AWS, Stripe, OpenAI and GitHub tokens. Free for public repos.
4
Store secrets in environment variables
Never hardcode secrets in source files. Use .env locally (gitignored), and your hosting platform's env config in production (Vercel, Netlify, Railway, Heroku all support this).

Before You Share the Code — Sanitize First

If you need to share code, logs or stack traces for debugging — with a colleague, on StackOverflow, or with an AI assistant — run it through a sanitizer first to catch any remaining secrets.

Sanitize Code Before Sharing

Strip API keys, emails and credentials from logs and stack traces. 100% client-side — nothing leaves your browser.

FAQ

How do I remove an API key from Git history? +

Revoke the key first, then use git filter-repo --replace-text secrets.txt or BFG Repo-Cleaner to rewrite all commits. Force-push the cleaned history. Tell all collaborators to re-clone.

Is deleting the file enough to remove a secret from Git? +

No. Deleting a file only removes it from the latest commit. Every previous commit still contains the secret. You must rewrite history with git-filter-repo or BFG to remove it from all commits.

What should I do first if I committed an API key? +

Revoke and rotate the key immediately — assume it is already compromised. Bots scan public GitHub commits in real time. History cleanup is important but secondary to revoking the key.

What is the difference between git-filter-repo and BFG? +

git-filter-repo is the modern recommended tool — Python-based, no Java required, and GitHub's official recommendation. BFG is simpler to use but requires Java. Avoid the old git filter-branch — it is slow and deprecated.

How do I stop API keys being committed to Git in future? +

Add .env to .gitignore, use a pre-commit hook with detect-secrets or gitleaks, and enable GitHub Secret Scanning with push protection in your repo settings.

Related