← All posts
How-to

Find the secrets already in your git history

Deleting a key from a file doesn't remove it from the repo. It's still sitting in an old commit, and anyone who can clone can read it. Here's how to find what's in there, and why rewriting history is the wrong first move.

Someone commits an API key. They notice an hour later, delete the line, commit again, and move on feeling fine about it.

The key is still in the repo. It's in the old commit, where it will stay forever, readable by anyone who can clone. git log -p will show it to them.

If your repo has been around a while, there's a decent chance something is in there. Here's how to look.

The quick scan

The fastest thing with no tools to install:

git log -p --all -S 'AKIA' --oneline

-S finds commits where the number of occurrences of that string changed. So this shows you every commit that added or removed something containing AKIA, which is the prefix on every AWS access key id.

Swap the string for whatever you want to look for:

git log -p --all -S 'BEGIN RSA PRIVATE KEY' --oneline
git log -p --all -S 'xoxb-' --oneline
git log -p --all -S 'sk_live_' --oneline

To search the full contents of every commit rather than just the changes:

git grep -I -n 'AKIA' $(git rev-list --all)

That's slow on a big repo. Let it run.

The proper scan

For anything beyond a spot check, use a scanner. gitleaks is the one I reach for:

brew install gitleaks
gitleaks detect --source . -v

It walks the whole history and knows the shapes of a few hundred credential types, which beats guessing prefixes. Run it once on each repo you own. It takes minutes and the first run is usually educational.

Now the important bit

Your instinct will be to clean the history. Resist it for a second, because the order matters here.

Rotate the key first. Always. Before anything else.

If that credential was ever pushed anywhere, cleaning history does not get it back. Here's why:

  • Anyone who cloned has a full copy, including every commit you're about to delete.
  • Forks have it.
  • CI caches and build artefacts have it.
  • On GitHub, a commit that's no longer reachable from any branch is often still readable by its SHA. Force-pushing doesn't immediately delete it.
  • If the repo was ever public, assume a scraper got it within minutes. People run bots specifically for this, and they're fast.

So rewriting history is housekeeping. It's worth doing, it's just not the remediation. The remediation is a new credential and a look at what the old one did while you weren't watching.

Then clean up

Once the key is dead and can't hurt you, use git-filter-repo:

git filter-repo --replace-text secrets.txt

Where secrets.txt has one literal:thekey==>REMOVED line per thing you're scrubbing. Everyone with a clone will need to re-clone afterwards, so warn the team first.

Stopping the next one

Two things, both cheap.

A pre-commit hook so the key never lands:

gitleaks protect --staged -v

And a .gitignore that covers .env, *.pem, *.key, and whatever your stack calls its credentials file. Check what's already tracked:

git ls-files | grep -iE '\.(env|pem|key|p12|pfx)$'

If that returns anything, you've found your afternoon.

One last thing. Secrets don't only escape through git. They also go out in pasted logs, tickets and chat. If you want to catch those before they leave, the prompt sanitizer masks them in anything you're about to paste, in your browser.

How-toBreaches