All posts

Field guide

How to remove a secret from git history (deleting the file isn't enough)

You spotted a key in your repo, deleted the file, committed, and felt better. The key is still there. Git history keeps every past version, so the secret is one checkout away. Here is how to actually purge it, and the step you cannot skip afterward.

Last updated

Here is the trap: you find an API key committed to your repo, you delete the file or edit it out, you commit the change, and it looks clean. It is not. Git stores history as a chain of commits, and every past commit that contained the key still contains it. Anyone can check out an earlier commit and read the secret. To truly remove it, you have to rewrite history, and then do one more thing that matters even more.

Step 1: rewrite history to purge the secret

Use a history-rewriting tool to remove the secret from every commit. The modern recommendation is git filter-repo:

# install git-filter-repo (e.g. pip install git-filter-repo), then:

# remove an entire file from all history
git filter-repo --path config/secrets.js --invert-paths

# or replace a specific leaked string everywhere
echo "sk_live_ABC123==>REMOVED" > replacements.txt
git filter-repo --replace-text replacements.txt

BFG Repo-Cleaner is a simpler alternative for the same job:

# remove a file, or scrub a set of secrets listed in a file
bfg --delete-files secrets.js
bfg --replace-text secrets.txt

Both rewrite every affected commit so the secret no longer appears anywhere in history.

Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.

See a live scan →

Step 2: force-push the cleaned history

Rewriting history changes commit hashes, so you push the new history over the old and clean up references:

git push --force origin main

# then expire and prune the old objects locally
git reflog expire --expire=now --all
git gc --prune=now --aggressive

If you work with others, coordinate this: everyone should re-clone afterward so no one force-pushes the old commits back.

Step 3: rotate the key (this is the important one)

Rewriting history cleans your repo, but it does not undo the exposure. If the secret was ever pushed to a remote, especially a public one, treat it as already compromised. Bots scan public commits within minutes, and a rewrite cannot recall a copy someone already grabbed. So rotate the key: generate a new one, update it wherever your app reads it from, and revoke the old one so the leaked value stops working. The full recovery checklist is in this guide, and the reason speed matters is in what actually happens when you push a key.

Then make sure it can't happen again

Prevention beats cleanup. Gitignore your .env before the first commit, add a pre-commit secret scanner so a key fails the commit, and read secrets from environment variables instead of inlining them. The four-layer setup is in how to stop AI tools from hardcoding your secrets.

Frequently asked questions

I deleted the file and committed. Isn't the secret gone?

No. Deleting a file in a new commit removes it from the current version, but every past commit still contains it, and anyone can check out an old commit and read it. Git history is append-only by default, so removing a secret means rewriting history, not just adding a commit that deletes the file. Until you rewrite history, the key is still there.

Do I still need to rotate the key if I rewrite history?

Yes, always, and this is the part people skip. If the repo was ever pushed to a remote, especially a public one, assume the secret was already read. Automated bots scan public commits within minutes, and rewriting history does not recall a copy someone already cloned or cached. Rewriting history cleans your repo; rotating the key is what actually protects you.

filter-repo or BFG, which should I use?

Both work. git filter-repo is the modern, official recommendation and is fast and flexible. BFG Repo-Cleaner is a simpler, purpose-built tool that is very good at removing secrets and large files. If you are removing a specific string or file, either is fine. filter-repo is the safer default going forward since the older git filter-branch is slow and error-prone.

Will rewriting history break things for my team?

It can, because everyone's local clones still have the old history. After you force-push the rewritten history, collaborators need to re-clone or carefully reset, and any open pull requests may need to be recreated. On a solo project it is painless. On a team, coordinate the rewrite, and afterward have everyone re-clone to avoid reintroducing the old commits.

Find what's already exposed

Before you rewrite anything, it helps to know exactly what is in there. A scan reads the repo the way an attacker's bot does and hands you the file and line for every exposed secret, so you can purge and rotate the right ones. Run a free scan and see what is sitting in your history.

Prbl scans your live app or your codebase for exactly the kinds of issues above.

How to Remove a Secret From Git History (Deleting the File Isn't Enough): Prbl