Skip to content

Photo: Luca Bravo / Unsplash

← All guides
GUIDE / SECRETS / GIT

How do I remove passwords and API keys from the Git history?

SHORT ANSWER

First revoke the key and generate a new one – that's the most important step, because copies may have existed for a long time. Then clean up the history with git filter-repo, push it again, round up all old copies and make sure it doesn't happen again.

Why deleting alone isn't enough

A new commit that removes the key doesn't change the past: it's still in the history, and anyone who clones the repository can find it.

  • Public repositories are scanned for keys automatically – often within minutes of the push.
  • Clones, forks, CI caches and backups still contain the key.

That's why the rule is: a key that was in the history once is burned. Cleaning up the history is tidying up, not rescue.

Step 1: Revoke the key and generate a new one

  • Revoke the key with the provider – OpenAI, Stripe, AWS, Supabase or whoever issued it – and generate a new one.
  • Store the new key as an environment variable with your host or in your CI, not in the code.
  • Check the provider's logs and billing: was the old key used by strangers?

Step 2: Find out what else is in the history

It's rarely just one key. The tool gitleaks scans the entire history:

gitleaks git -v

Older versions use gitleaks detect for this. Every hit goes on the list: revoke, regenerate, remove from the history later.

Step 3: Clean up the history

The recommended tool is git filter-repo. It rewrites the history – so make a backup first:

git clone --mirror git@github.com:company/app.git app-backup.git

Then work in a fresh clone. To remove a file like .env from the history completely:

git filter-repo --invert-paths --path .env

To replace individual keys everywhere – a text file lists one replacement per line:

echo 'MY-SECRET-KEY==>REMOVED' > replacements.txt
git filter-repo --replace-text replacements.txt

git filter-repo then deliberately removes the connection to the server. So reconnect and push the cleaned-up state:

git remote add origin git@github.com:company/app.git
git push --force --all
git push --force --tags

If the main branch is protected against force pushes, the protection has to be lifted briefly for this. An alternative to filter-repo is the BFG Repo-Cleaner.

Step 4: Round up all old copies

  • Everyone on the team deletes their old clones and clones again. Otherwise the next push brings the old history back.
  • On GitHub, references in pull requests and cached views may persist. Only GitHub Support can remove them.
  • Clear the CI caches and artifacts.

Step 5: Make sure it doesn't happen again

  • Add .env and similar files to the .gitignore and put a .env.example without real values in the repository.
  • A pre-commit hook with gitleaks stops commits containing keys before they're created.
  • Enable secret scanning and push protection on GitHub or GitLab, if your plan offers it.
  • Add the secret scan to your automatic test pipeline – see What is CI/CD and why do I need it for vibe coding?
  • Write down in the rules for your AI assistants that keys are only included via environment variables.

Not sure whether something is still lurking in your repository? The Operations Check scans code and history for keys and shows what needs to be done.