Git Pushed Sensitive API Key by Accident, How to Purge From Commit History Completely
Accidentally committing and pushing a real API key or secret requires two separate actions, and the order matters: rotating the exposed credential immediately is the actual security fix, while rewriting Git history to remove it is important cleanup that should never be treated as a substitute for rotation.
The Problem
A commit containing a real, sensitive credential was pushed to a remote repository:
git log -p -- config.js
+ const API_KEY = "sk_live_abc123realkey456";
Even after deleting the key from the current version of the file and committing that deletion, the original value remains fully visible in the repository's history, retrievable by anyone with access to the repo.
Why It Happens
Git is designed to preserve history — every commit remains part of the repository's permanent record unless that history is deliberately and explicitly rewritten. Simply deleting the secret in a new commit doesn't remove it from history at all; it only means the current version of the file no longer contains it. Anyone with access to the repository can still view the old commit directly and retrieve the exposed value, indefinitely, unless the history itself is rewritten.
The Fix
First and most importantly: rotate the exposed credential immediately, treating it as compromised the moment it was pushed, regardless of whether you've noticed any actual misuse yet. This is the real fix — history cleanup afterward is important but secondary:
# Steps vary by provider, but generally:
# 1. Generate a new key/credential in the provider's dashboard
# 2. Update your application's actual configuration with the new value
# 3. Revoke/delete the old, exposed credential
Check the provider's usage logs for any suspicious activity during the exposure window, since rotating the key prevents future misuse but doesn't undo anything that may have already happened while it was live and exposed.
Once the credential is rotated, clean up the repository's history. The modern, recommended tool for this is git filter-repo (the officially recommended replacement for the older git filter-branch, which is both slower and has several documented pitfalls):
pip install git-filter-repo
git filter-repo --replace-text <(echo "sk_live_abc123realkey456==>REMOVED")
This rewrites every commit in the repository's history, replacing the exposed value with a placeholder wherever it appears — this is a genuine history rewrite, changing every commit hash from that point forward, not simply adding a new cleanup commit on top of the existing history.
Alternatively, the BFG Repo-Cleaner is a widely used, purpose-built tool specifically for this kind of cleanup, often simpler to use for straightforward secret-removal cases:
bfg --replace-text passwords.txt my-repo.git
Where passwords.txt contains the exact sensitive strings to find and replace throughout history.
After rewriting history locally, force-push the rewritten history to the remote, since a normal push won't work for history that no longer matches the remote's existing commit graph:
git push origin --force --all
git push origin --force --tags
This is a genuinely disruptive operation for anyone else who has already cloned or fetched the repository — their local copies still contain the old history with the exposed secret, and they'll need to re-clone the repository fresh (not just pull) to get the cleaned version. Coordinate this clearly with your team before force-pushing rewritten history to a shared repository.
Clear any cached views of the old commits that GitHub, GitLab, or your specific hosting provider might retain independently of the Git history itself, since some platforms cache commit data (including pull request diffs) that can persist even after a force-push rewrites the underlying repository — check your specific provider's documentation for how to request removal of cached data if this applies to your situation.
Still Not Working?
If the repository has many forks or clones already in the wild (a public open-source project, for example), be aware that a history rewrite on the origin repository doesn't retroactively remove the secret from any fork or clone that already exists elsewhere — those copies retain the old history independently, and the exposed credential remains recoverable from them regardless of what you do to the origin repository. In this scenario, credential rotation isn't just the primary fix — for a sufficiently public exposure, it may be the only fix that's actually enforceable, since you generally can't force removal from every downstream copy that already exists.