How to remove an API key from git history
Last verified
Rewriting history does not disable the credential. If you have not revoked the key yet, stop and do that first — how to revoke an OpenAI API key. This page is cleanup.
Option 1 — git-filter-repo (recommended)
The tool git's own maintainers point to. Fast, and it fails loudly rather than silently doing the wrong thing.
pip install git-filter-repo
# work on a fresh mirror clone, not your working copy
git clone --mirror https://github.com/you/repo.git repo-mirror
cd repo-mirror
To remove a file that contained the key everywhere it ever existed:
git filter-repo --invert-paths --path .env --path config/secrets.json
To replace the key string itself, wherever it appears — better when the secret was inline in a source file you want to keep:
cat > /tmp/expressions.txt <<'EOF'
sk-proj-YOUR-ACTUAL-LEAKED-KEY==>REDACTED
sk-ant-YOUR-OTHER-KEY==>REDACTED
EOF
git filter-repo --replace-text /tmp/expressions.txt
Then push the rewritten history:
git push --force
Delete /tmp/expressions.txt afterwards. It contains your leaked keys in plaintext, which is a mildly absurd way to leak them a second time.
Option 2 — BFG Repo-Cleaner
Simpler for the common case, and considerably faster on large repositories.
git clone --mirror https://github.com/you/repo.git
bfg --replace-text passwords.txt repo.git # one secret per line
# or
bfg --delete-files .env repo.git
cd repo.git
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push --force
The parts that don't get mentioned
Rewriting history changes every commit SHA after the rewrite point. That has consequences.
Everyone must re-clone. Do not let collaborators merge or rebase their old copies — that reintroduces the removed commits. Tell them explicitly to delete and re-clone.
Open pull requests break. They reference commits that no longer exist. Expect to close and reopen them.
Forks keep the secret. You have no ability to rewrite someone else's fork. If the repository has forks, the key is still out there in them.
GitHub caches detached commits. After a force-push, orphaned commits can remain reachable by direct SHA until garbage collection. That is not immediate and not under your control. For a public repository, GitHub Support can be asked to run GC and purge cached views — but treat that as tidying, not remediation.
Tags and other refs need attention. --mirror covers them; a plain clone does not.
Verify
git log --all --full-history -p -S 'sk-proj-' | head
git log --all --full-history -- .env
Both should come back empty. And run a scanner over the full history to catch what you didn't know was there:
gitleaks detect --source . --log-opts="--all"
trufflehog git file://. --only-verified
--only-verified on trufflehog is the useful flag: it tells you which discovered credentials are _still live_, which is the list that actually matters.
Then stop it recurring
.gitignorefor.env,.env.*,*.pem,secrets/— added when you create the repodetect-secretsorgitleaksas a pre-commit hook- GitHub push protection: Settings → Code security
- Secrets from environment variables only, with no literal keys in source ever
And accept that prevention eventually fails. What decides whether the next leak costs $5 or $5,000 is how quickly someone notices — which is a monitoring problem, not a git problem. TKN exists for that half.