Removing a secret in a later commit does not remove it from the repository. Git keeps every version of every file as an immutable object keyed by its content hash, so a key, token, or password that was ever committed stays reachable through history, through objects no branch points to any more, and through the reflog, as long as you hold the repository. A .git you copied or dumped carries that full object store with it; a fresh anonymous clone carries only the objects reachable from the published refs, not the unreferenced ones or the origin's reflogs.
Search the committed history#
The pickaxe (-S/-G) finds the exact commit that introduced or removed a string, across every ref, which is where "I deleted the password in the next commit" cleanups fall down:
git log -p --all -S 'password' # commits that add/remove the literal "password"
git log -p --all -G 'AKIA[0-9A-Z]{16}' # regex: an AWS access-key id anywhere in a diff
git log --all --oneline -- config/secrets.yml # every revision of a sensitive path, then git show each
--all walks all refs rather than just the current branch, and --source names the ref each hit lives on.
Recover dangling and force-pushed objects#
Commits removed by git rebase, git commit --amend, or a force-push become unreferenced but remain in the object database of a repository that held them before the rewrite: your own long-lived working clone, or a .git you dumped while the objects were still present. git fsck surfaces them, and the local reflog records where your branches pointed before a reset or amend:
git fsck --lost-found --dangling 2>/dev/null # dangling commit/blob SHAs present in THIS repo
git cat-file -p <dangling-sha> # read the content directly
git reflog --all # your local ref history (not the server's)
A dangling blob that git log cannot reach is where a rewritten "fix: remove secret" commit left the original value. Note the limit: a clone taken after the server force-pushed does not receive the dropped objects, and its reflog holds only your own updates, so recover these from a repository you held earlier or from a dumped .git, not from a fresh clone.
Automate it#
Run a dedicated scanner over the full history rather than grepping blind; they carry hundreds of credential patterns and can verify live keys:
trufflehog git file://./loot --only-verified # scan a local clone/dump, confirm which creds still work
gitleaks detect --source . --log-opts '--all' # regex + entropy across all refs
Follow-on#
A recovered credential is immediate reuse material: database and cloud keys, CI tokens, SSH deploy keys, and API secrets. Validate it, then pivot to whatever it unlocks. Keys that were "rotated" after the leak are worth testing anyway, rotation is frequently incomplete.