要从 git 历史中删除密码,需要用 git filter-repo --invert-paths --path <file> 重写包含它的所有提交,然后让 reflog 过期并执行垃圾回收,这样旧提交才会真正从 .git 中消失。这些操作应该在轮换密钥之后再做——一旦密钥被提交,就应视为已经泄露,无论你之后是否清理历史。.gitignore 和 git rm --cached 对已经存在的提交毫无作用。
1. 确认密钥是否还留在历史中
$ clai 检查这个密钥是否还留在仓库历史的某个地方→ git log --all --oneline -S 'sk-live-abc123secret'0fb8c94 stop tracking .env4c1d9ff add app and env
-S 会找出该字符串出现次数发生变化的提交——也就是密钥被加入和被移除的那些提交。它搜索的是所有分支的完整历史,而不是工作区。
2. 确认从索引中删除并没有用
$ clai 从添加它的那个提交中取出 .env 文件→ git show 4c1d9ff:.envAPI_KEY=sk-live-abc123secret
问题全都在这一条命令里。.env 已经从工作区删除,写进了 .gitignore,git rm --cached 也执行过了——可任何拥有克隆的人,一条命令就能把密钥取出来。提交里仍然保存着完整内容。
3. 看看哪些提交涉及这个路径
$ clai 显示所有提到 .env 文件的提交→ git log --all --oneline -- .env0fb8c94 stop tracking .env4c1d9ff add app and env
这就是需要重写的清单。只有两个提交,你会很轻松。要是两百个还分散在不同分支上,那得先和整个团队协调。
4. 先轮换,再清理
一旦密钥进了 git,就必须当作已经泄露处理,没有例外。仓库可能在你发现问题之前就已经被克隆、fork、被爬虫索引,或者被 CI 缓存过。先撤销旧密钥、签发新密钥,再动历史记录:一个干净的历史配上一个仍然有效的被盗密钥,比一个混乱的历史配上一个已经失效的密钥要糟糕得多。
5. 用正确的工具重写历史
$ clai 从整个仓库历史中删除 .env 文件→ git filter-repo --invert-paths --path .env⚠ DANGER — rewrites all history, every commit hash changes
git filter-repo 是 git 项目本身推荐的工具。git filter-branch 已被弃用:速度慢了几个数量级,使用不当还可能悄无声息地损坏仓库。BFG Repo-Cleaner 是另一个选择:在大型仓库上更快,还能把字符串替换成 ***REMOVED***,而不必删除整个文件。
6. 清理本地残留的痕迹
$ clai 让旧引用过期并强制执行垃圾回收→ git reflog expire --expire=now --all && git gc --prune=now --aggressive
重写之后,旧提交仍然可以通过 reflog 访问到——也就是说密钥依然留在 .git 里。这条命令会把它们彻底清除。在 GitHub 或 GitLab 上,你还得另外联系支持团队清空缓存,而 fork 出去的仓库没有人会帮你清理。
常见陷阱
- 重写过的历史会破坏团队里每一个克隆。 force-push 之后,每个人都得重新克隆,或者执行
git rebase --onto。提前告知大家,不然总有人会把旧历史再推回去。 - fork 和 pull request 不受影响。 在 GitHub 上,即使主仓库已经清理干净,来自已关闭 PR 的提交仍可能通过直接链接访问到。这也是要轮换密钥的另一个理由。
git filter-branch已被弃用。 git 本身运行时就会打印警告,指向 filter-repo。搜索结果里一半的文章还不知道这件事。
相关问题
把文件加进 .gitignore 够不够? 不够。.gitignore 只影响新文件,已经提交过的内容依然完整地留在历史里。
以后怎么防止这种事? 用带有密钥扫描器的 pre-commit 钩子:gitleaks、detect-secrets 或 trufflehog。它们能在提交之前拦下密钥,而不是等它公开之后。
如果密钥已经出现在 GitHub 上怎么办? 立刻撤销密钥,然后清理历史,再请 GitHub 支持清除缓存视图。顺序就是这样。
相关阅读
CliAI 把 git filter-repo 标记为 DANGER,在碰到任何一个提交之前都要求手动输入确认。一行命令安装。