Tutorial

How to Remove a Secret From Git History (The Safe Way)

You caught a key in your repo and deleted it from HEAD. The secret is still sitting in a commit from 2023 — readable by anyone with the clone URL. Here's how to actually erase it.

Git never forgets. Every commit is content-addressed: change a file, and the old version remains stored in history forever — or until you rewrite history itself. That's why the standard "delete the line, commit, push" flow fixes nothing for anyone willing to run git log -p. This walkthrough covers the complete purge, in the correct order.

Step 0: Revoke the credential

Before touching history, kill the key at its provider. History rewrites take time; a live key keeps working the whole way. Revoke first, clean second, always. If you need the full incident sequence, start with API Key Leaked? Here's What to Do.

Step 1: Find every occurrence

Before rewriting, know exactly what you're removing. Search all history for the secret and its variants:

$ git log --all -p | grep -n "AKIAIOSFODNN7EXAMPLE"
# or scan properly, across every ref:
$ vooda scan . --history

The variants matter. The key itself, yes — but also any placeholder, encoded form, or secondary credential that leaked alongside it.

Step 2: Rewrite history with git filter-repo

git filter-repo is the modern tool for this job — fast, safe, and it replaces both the deprecated filter-branch and the BFG for most purges. Install it, clone a fresh copy of the repo, and replace the secret everywhere:

$ git clone --mirror https://github.com/you/your-repo.git
$ cd your-repo.git
$ git filter-repo --replace-text <(echo "AKIAIOSFODNN7EXAMPLE==>***REMOVED***")

The --replace-text file maps literal strings to replacements and applies them across every commit, branch, and tag. For deleting entire files — an accidentally committed .env — use --path .env --invert-paths to drop the file from history altogether.

Step 3: Force-push and coordinate

Rewriting history invalidates every clone. Push the rewritten refs, then tell the team: everyone re-clones, nobody force-pulls their local copy back over yours. Forks — GitHub forks included — are separate repositories with the old history and must be handled individually. If the secret was public, treat every fork and cache as compromised.

Step 4: Verify like an attacker

After the rewrite, scan the remote history exactly the way an attacker would:

$ vooda scan . --history
✓ 0 findings — the secret is gone

If the scan still finds it — in a tag, a stale branch, an old remote — it's still exposed. Keep rewriting until nothing matches.

Why prevention beats purges

Purging history is the painful, disruptive fix. The cheap fix happens before the commit exists: a pre-commit scan that catches the secret while it's still a keystroke, push protection that blocks it if it slips through, and continuous history scanning that surfaces leaks the day they land — not the day an attacker finds them. Layered together, the next purge you perform will be someone else's. Vooda covers all three layers from a single self-hosted platform; the scan commands above are the real CLI.

Frequently asked questions

Does deleting a secret from the current code remove it from git history?

No. Git keeps every version of every file. A secret committed three years ago is still readable in that old commit until history itself is rewritten with a tool like git filter-repo.

Should I revoke the secret before rewriting git history?

Yes, always revoke first. Rewriting history takes time, and the credential keeps working the whole way. Kill it at the provider, then clean the repository.

What is the best tool to remove a secret from git history?

git filter-repo is the recommended modern tool — fast, safe, and maintained. It replaces filter-branch and the BFG for most purges, including string replacement across all history, branches, and tags.

Does force-pushing rewritten history affect other developers?

Yes. Every clone, fork, and local copy has the old history and must be re-cloned or reset. Coordinate before rewriting shared history, and re-check the remote afterward.

How do I verify the secret is really gone from git history?

Run a scanner across the rewritten history the way an attacker would. If it finds the credential in any commit, branch, or tag, it is still exposed.

Catch secrets before history makes them permanent

Pre-commit scans, push protection, and full-history scanning in one self-hosted platform — so purges are the exception, not the routine.