如何从 Git 历史中删除敏感信息
- Authors
- @SLIPPERTOPIA
这两天在开发一个通过 LLM 一键总结网页的 chrome 插件,测试过程中在本地添加了 API token,但忘记了把配置文件添加到 .gitignore
,开发了一段后回头查看提交记录时,才发现 token 已经被 commit 到历史记录中了。
在与 Claude Sonnet 进行一番探讨沟通之后,决定通过 git-filter-repo 来清理敏感信息,经过一番折腾并在 AI 的耐心指导下后终于成功的清除了 token,在此作个记录,以备不时之需。
#定位敏感信息
第一步是定位敏感信息所在的所有历史提交,这一步不是必须的,但可以辅助确认有哪些文件包含敏感信息,方便下一步编写替换规则,避免误伤。
操作可以通过 git rev-list --all
+ git grep
命令完成。
git rev-list --all
这个命令会列出所有的 commit hash- 然后再结合
git grep
命令去过滤查找敏感信息。
假定 token=sk-a1b2c3d4
是不幸泄露的敏感信息,对应的定位命令就是:
git rev-list --all | xargs git grep 'sk-a1b2c3d4'
或者:
git grep "sk-a1b2c3d4" $(git rev-list --all)
那么,所有包含 sk-a1b2c3d4
的 commit hash 都会被列出:
git grep "sk-a1b2c3d4" $(git rev-list --all)
2b1e4c406dfcf7dc8c892e9b87fbac7bb439d5f9:README.md:+token=sk-a1b2c3d4
26a64b6c8a79531c6ca9c848c650bc92b028d6d2:x.py:+token=sk-a1b2c3d4、
...
从这里大概可以看到一个概要,里面有包含敏感信息的 commit 及涉及文件。如果敏感信息是最近提交的,就比较好办了,先 git reset
到前一个 commit,然后重新提交。或者用 git rebase -i
命令,把包含敏感信息的 commit 之后的所有 commit 都 rebase 掉,然后重新编辑文件,删除敏感信息,重新提交。
但如果敏感信息是很早之前 commit 的,那就需要 git filter-repo
的文本替换功能。
#替换敏感信息
git-filter-repo 支持从文本文件中读取替换规则(通过 --replace-text
选项),并遍历所有历史提交,检查每个 blob 中的内容,然后根据指定的规则进行文本替换,类似于使用 sed
命令替换文本。
这些规则可以是正则表达式,也可以是简单的文本匹配。使用时务必谨慎,建议先用 git grep
确认替换范围,并尽可能使用最小粒度的替换规则。例如,如果泄露的 token 是 sk-a1b2c3d4
,那就用 sk-a1b2c3d4==>xxxxxxxx
这样的规则,而不是直接用 sk-*
替换 token
,因为这样会把所有包含 sk-
的行都替换掉,即使这些行并不包含敏感信息(比如 sk-II
这样的无关文本)。
初次使用需要安装 git-filter-repo
:
pip install git-filter-repo
下一步,创建替换规则文件 replace.txt
并执行替换:
echo "sk-a1b2c3d4==>xxxxxxxx" > replace.txt
git filter-repo --replace-text replace.txt
然后再查看历史:
git grep "sk-a1b2c3d4" $(git rev-list --all)
这时应该没有关于 sk-a1b2c3d4
的任何结果了,如果仍然能看到结果,需要检查替换规则是否正确。 最后再检查下替换后的文本,可以看到文本中所有的 sk-a1b2c3d4
都已经被替换成 xxxxxxxx
了。
git grep "xxxxxxxx" $(git rev-list --all)
2b1e4c406dfcf7dc8c892e9b87fbac7bb439d5f9:README.md:+token=xxxxxxxx
26a64b6c8a79531c6ca9c848c650bc92b028d6d2:x.py:+token=xxxxxxxx
##小坑
替换这一步有一个小坑需要注意,如果直接通过编辑器(比如 VSCode)编辑规则文件,那最终保存时文件可能不是以 UTF-8 编码存储的,这样会导致操作无效。笔者在这个地方卡了很久,一遍又一遍的改替换规则,正则表达式,但执行之后都没有效果。
如果是 Linux/Unix 系统,保险的做法是直接通过 echo
命令输出文本规则,然后重定向到文件。
echo "sk-a1b2c3d4==>xxxxxxxx" > replace.txt
#慎用 git-filter-repo
git-filter-repo
通过直接操作 Git 仓库的对象数据库(如 commits、trees 和 blobs)来实现历史记录的重写,除了替换文本,还可以做的事情很多,比如:
- 删除敏感信息、特定文件
- 重命名文件目录
- 修改提交信息,包括作者、日期、内容等
尽管 git-filter-repo
功能强大,但还是要慎用。原则上 commit 历史应该反映真实的开发历程,特别是公共仓库。已经被推送到远程仓库的 commits,它的内容就不应该变动。因为一旦变动,就会破坏 commit 历史的一致性,其他开发者也必需重新同步所有 commits,这相当于篡改了历史并修正了集体记忆,不仅不体面也不道德,这也是为什么有些开发者甚至连 rebase 都反对使用的原因之一。
Commit 是约束,是承诺,是契约,改掉了就相当于破坏了契约精神,破坏了大家都约定俗成的理念。
当然,如果是本地 commit,还没有推送到远程仓库,那就可以自由修改,因为还没有同步给其他协作者。可以 rebase,可以创建新分支,也可以使用 git-filter-repo
。
#小结
对于个人项目,清除敏感信息后,重新提交,然后推送到远程仓库即可。但如果是团队项目,还是着重防范于未然,规范流程,避免敏感信息泄露。
如果敏感信息已经被推送到远程,最妥善的处理办法还是假定信息已经泄露,立即重置所有相关令牌或密钥,因为代码一旦推送出去,它就不再属于你了,你永远不知道它被谁看到过。