CLI AI

Remove a password from git history

2026-06-01

Removing a password from git history means rewriting the commits that contain it with git filter-repo --invert-paths --path <file>, then expiring the reflog and running garbage collection so the old commits are gone from .git too. Do that only after you've rotated the key — a leaked secret is compromised the moment it's committed, whether or not you ever clean the history. .gitignore and git rm --cached don't touch commits that already exist.

1. Check whether the secret is still in history

clai
$ clai check if this key is still anywhere in the repository history→ git log --all --oneline -S 'sk-live-abc123secret'0fb8c94 stop tracking .env4c1d9ff add app and env

-S finds commits where the number of times that string appears changed — where the secret was added and where it disappeared. It searches the full history of every branch, not the working tree.

2. Confirm removing it from the index didn't help

clai
$ clai get the .env file from the commit where it was added→ git show 4c1d9ff:.envAPI_KEY=sk-live-abc123secret

This is the whole problem in one command. .env is gone from the working tree, listed in .gitignore, and git rm --cached has already run — yet anyone with a clone can pull the secret out with a single command. The commit still holds the full content.

3. See which commits touch the path

clai
$ clai show every commit that mentions the .env file→ git log --all --oneline -- .env0fb8c94 stop tracking .env4c1d9ff add app and env

That's the list of what needs rewriting. Two commits and you get off easy. Two hundred spread across branches and you're coordinating with the whole team first.

4. Rotate first, clean up second

A secret that made it into git has to be treated as leaked, period. The repository may already be cloned, forked, indexed by a bot, or cached by a CI runner before you even notice the mistake. Revoke the key and issue a new one before touching history — a clean history with a live stolen key is worse than a messy history with a key that's already dead.

5. Rewrite history with the right tool

clai
$ clai remove the .env file from the entire repository history→ git filter-repo --invert-paths --path .env⚠ DANGER — rewrites all history, every commit hash changes

git filter-repo is what the git project itself recommends. git filter-branch is deprecated — it's orders of magnitude slower and can silently corrupt a repository if you're not careful. BFG Repo-Cleaner is the other option: faster on large repos, and it can replace a string with ***REMOVED*** instead of deleting the whole file.

6. Clean up what's left locally

clai
$ clai expire old refs and run garbage collection aggressively→ git reflog expire --expire=now --all && git gc --prune=now --aggressive

After a rewrite, the old commits stay reachable through the reflog — meaning the secret is still sitting in .git. This clears them out for good. On GitHub or GitLab you'll still need to ask support to purge their cache, and nobody clears the forks for you.

Gotchas

  • A rewritten history breaks every clone on the team. After the force-push, everyone has to re-clone or git rebase --onto. Warn people ahead of time, or someone will push the old history right back.
  • Forks and pull requests survive. On GitHub, a commit from a closed PR can stay reachable by direct link even after the main repository is cleaned. One more reason to rotate the key.
  • git filter-branch is deprecated. Git itself prints a warning and points you at filter-repo when you run it. Half the articles ranking for this still don't know that.

Related questions

Is adding the file to .gitignore enough? No. .gitignore only affects new files. Anything already committed stays in history in full.

How do I stop this from happening again? A pre-commit hook with a secret scanner — gitleaks, detect-secrets, or trufflehog. They catch the key before the commit, not after it's public.

What if the secret is already on GitHub? Revoke the key immediately, then clean the history, then ask GitHub support to clear cached views. That's the order.

See also

CliAI classifies git filter-repo as DANGER and asks for a typed confirmation before it touches a single commit. Install it in one line.