
๐ค Ghostwritten by Claude Opus 4.6 ยท Fact-checked & edited by GPT 5.4
Deleting a secret from the latest version of a repository does not remove it from Git. If a credential was ever committed, it remains recoverable from prior commits unless the repository's history is rewritten. The correct response order is simple: rotate or revoke the secret first, then scrub Git history as cleanup. BFG Repo-Cleaner and git-filter-repo can both remove leaked values from past commits, but neither tool makes an already-exposed credential safe again. This guide explains why Git retains old data, when to use each tool, and how to clean up without making the incident worse.
TL;DR: Git stores commit snapshots as immutable history, so removing a secret in a later commit does not erase it from earlier ones.
Git does not work like a word processor where the latest save replaces the old one. Each commit records a snapshot of the repository state, and those snapshots are linked together through commit history. When a secret is deleted in a later commit, Git adds a new snapshot without that secret, but the earlier commit that contained it still exists.
If a file once contained STRIPE_SECRET_KEY=sk_live_abc123 and that line was later removed, anyone with access to the repository history can still inspect the old commit with commands like git log -p or git show <commit>.
That behavior is not a flaw. It is central to Git's design and is what enables workflows like git revert, git bisect, and historical debugging. The downside is straightforward: a secret that was committed, even briefly, should be treated as exposed.
In May 2026, GitGuardian published a case study describing plaintext secrets discovered in the history of a public repository associated with CISA. GitGuardian reported that the exposed data had persisted in repository history for months and that some credentials were still valid when found. The incident is a useful reminder that the risk often comes from ordinary Git behavior, not from an advanced intrusion technique.
TL;DR: History cleanup is important, but revoking or rotating the leaked credential is the urgent security action.
This order matters for three reasons:
The practical rule is simple: assume a pushed secret is compromised. Generate a replacement, revoke the old value, update deployments, and only then proceed with repository cleanup.
TL;DR: BFG is simpler for common cleanup jobs; git-filter-repo is more flexible and is widely recommended over git filter-branch.
Both tools rewrite repository history by creating new commits that omit or replace targeted content. That means commit hashes change, and the rewritten history must be force-pushed.
| Feature | BFG Repo-Cleaner | git-filter-repo |
|---|---|---|
| Runtime | Java | Python 3 |
| Typical use | Fast removal of known secrets or large files | Flexible filtering and more complex rewrites |
| Ease of use | Simpler flags | More control, steeper learning curve |
| Matching options | File and text replacement workflows | Path filters, text replacement, regex-capable workflows |
| Project status | Mature, less active | Actively maintained |
| Best fit | Quick cleanup of known values | More surgical or repeatable history rewrites |
BFG is often the fastest path when the leaked value is known and the goal is straightforward replacement.
## Create a file listing the exact text to replace
printf 'sk_live_abc123==>REVOKED_SECRET\n' > secrets.txt
## Work from a mirror clone
git clone --mirror <REMOTE_URL> repo.git
java -jar bfg.jar --replace-text secrets.txt repo.git
cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force --mirrorgit-filter-repo is better suited to cases where cleanup needs more control, such as repeated patterns, path-specific filtering, or more complex repository surgery.
## Install
pip install git-filter-repo
## Work from a fresh mirror clone
git clone --mirror <REMOTE_URL> repo.git
cd repo.git
## Replace known text across history
git filter-repo --replace-text <(printf 'sk_live_abc123==>REVOKED_SECRET\n')
git push --force --mirrorImportant: Rewriting history changes commit IDs throughout the repository. Collaborators usually need to fetch the new history and, in many cases, re-clone or carefully rebase local work onto the rewritten repository.
TL;DR: Use a fixed sequence: revoke, assess scope, rewrite history, force-push carefully, then add guardrails to prevent a repeat.
Go to the affected provider and invalidate the exposed credential. Create a replacement, update the application or infrastructure that depends on it, and confirm the new credential works before moving on.
Before rewriting anything, determine what leaked and where it appears. Secret scanners can help identify both the known credential and any additional exposures.
## Example: gitleaks against a local repository
gitleaks detect --source . --verbose## Example: trufflehog against a local git repository
trufflehog git file://$(pwd)Use a fresh mirror clone rather than a normal working copy. Verify that the targeted values are removed from all relevant commits before pushing changes upstream.
After validation, force-push the rewritten repository. For mirrored clones, git push --force --mirror is usually the safest match for the clone type used during cleanup.
Anyone with an older clone still has the contaminated history locally. Teams should either re-clone or follow a carefully documented recovery process for rebasing in-progress work onto the new history.
Use pre-commit scanning, CI secret scanning, and a secrets manager so credentials do not land in source control again.
## Example .pre-commit-config.yaml entry for gitleaks
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaksAlso consider .gitignore rules for local secret files and repository-level secret scanning in the hosting platform.
No. Deleting the file only affects later commits. Earlier commits still contain the original content unless history is rewritten.
Use BFG when the job is simple and the leaked value is known. Use git-filter-repo when you need more control, repeatability, or more advanced filtering behavior.
Usually yes. Rotation addresses the immediate security risk, but history cleanup reduces future scanner noise, prevents accidental reuse, and removes sensitive material from the main repository lineage.
Not always, but it is often the cleanest option. Small teams with disciplined Git workflows may recover by fetching the rewritten history and rebasing local work. For many teams, re-cloning is simpler and less error-prone.
Combine local pre-commit hooks, CI scanning, least-privilege credentials, short-lived tokens where possible, and a dedicated secrets manager. Prevention works best when it is automated rather than left to memory.
git-filter-repo offers more control.Cleaning leaked secrets from Git history is a repository hygiene task, not the primary security fix. The real fix is revoking the exposed credential quickly and replacing it safely. After that, a careful history rewrite with BFG or git-filter-repo can remove the leaked value from the repository's visible lineage, reduce future confusion, and help restore trust in the codebase. Teams that automate secret detection before code is committed avoid the most painful part of this process altogether.
Discover more content: