关于从仓库中删除敏感数据
在使用 git-filter-repo 等工具更改仓库历史时,必须了解其影响。重写历史需要与协作者进行仔细协调才能成功执行,并且会产生需要管理的多种副作用。
需要特别注意的是,如果您要删除的敏感数据是密码、令牌或凭证等机密信息(这往往是最常见的情况),第一步应该是撤销或轮换该机密。机密被撤销或轮换后将不再可用于访问,这可能已经足以解决问题。若仅为此目的而进行历史重写并删除机密,往往并非必要。
重写历史的副作用
重写历史会产生多种副作用,主要包括:
- 高风险的再污染:不小心将敏感数据重新推送到仓库是很常见的错误,尤其是如果其他开发者仍持有重写前的克隆仓库,并在您完成重写后仅执行
git pull再git push,敏感数据会再次出现。此时他们必须丢弃本地克隆并重新克隆,或按照多个步骤手动清理本地副本。 - 丢失其他开发者工作风险:如果其他开发者在您清理期间继续在包含敏感数据的分支上提交,您将被迫重新进行清理,或只能舍弃他们的工作。
- 提交哈希改变:重写历史会更改引入敏感数据的提交以及其之后所有提交的哈希。任何依赖提交哈希保持不变的工具或自动化流程都会因此失效或出现问题。
- 分支保护挑战:如果仓库启用了阻止强制推送的分支保护,在删除敏感数据之前必须临时关闭这些保护。
- 已关闭 Pull Request 的 diff 视图失效:删除敏感数据会导致用于渲染 Pull Request diff 的内部引用被移除,从而无法再查看这些 diff。这不仅影响最初引入敏感数据的 PR,还会影响所有基于该历史后续版本的 PR,即使后续 PR 并未修改任何敏感文件。
- 与打开的 Pull Request 交互受阻:提交 SHA 的改变会导致 PR diff 变化,原有的评论可能失效或丢失,给作者和审阅者带来混乱。建议在从仓库中删除文件前,先合并或关闭所有打开的 Pull Request。
- 提交和标签签名丢失:提交或标签的签名依赖于提交哈希;历史重写会修改哈希,从而导致签名失效。大多数历史重写工具(包括
git-filter-repo)会直接删除这些签名。事实上,git-filter-repo甚至会删除在敏感数据删除之前的提交和标签的签名。(如果确有需要,可以使用--refs选项指定所有包含敏感数据的引用来规避,但这需要非常小心地列出所有相关引用) - 直接指向敏感数据的线索:Git 的设计在提交标识符中内置了加密校验,以防止恶意用户在不被发现的情况下篡改服务器历史。这对安全是有益的,但在需要清除敏感数据时意味着必须进行高度协同的操作;拥有已有克隆的用户会注意到历史分叉,并能够快速定位仍然保存在他们本地克隆中的敏感数据。
关于敏感数据泄露
从仓库中删除敏感数据大致包括四个步骤
- 使用 git-filter-repo 在本地重写仓库历史
- 将本地重写后的历史推送至 GitHub,更新远端仓库
- 与同事协作,清理他们本地的克隆副本
- 防止再次泄露并避免将来出现敏感数据泄漏
仅在本地重写历史并强制推送后,包含敏感数据的提交仍可能在其他位置被访问到
- 在任何克隆或分叉的仓库中
- 通过 GitHub 缓存视图中暴露的 SHA-1 哈希直接访问
- 通过任何引用这些提交的 Pull Request
您无法从其他用户的克隆仓库中删除敏感数据;请让他们参考 Make sure other copies are cleaned up: clones of colleagues(在 git-filter-repo 手册中的相关章节),自行执行清理。不过,您可以通过 GitHub 支持门户 联系我们,帮助永久删除 GitHub 上的缓存视图和 Pull Request 中对敏感数据的引用。
重要提示
GitHub 支持不会删除非敏感数据,仅在我们判断仅通过轮换受影响凭证仍无法降低风险的情况下,帮助删除敏感数据。
如果引入敏感数据的提交已经出现在任何分叉中,该提交仍将在分叉中可访问。您需要与这些分叉的所有者联系,要求他们删除敏感数据或直接删除整个分叉。GitHub 无法提供这些所有者的联系信息。
在决定是否对仓库历史进行重写时,请考虑上述限制和挑战。
使用 git-filter-repo 从本地仓库历史中彻底清除文件
-
安装最新版本的 git-filter-repo 工具。请确保使用带有
--sensitive-data-removal标志的版本(即最低 2.47 版)。您可以手动安装git-filter-repo,也可以使用包管理器进行安装。例如,使用 HomeBrew 可执行brew install命令。brew install git-filter-repo更多信息请参阅 INSTALL.md(位于
newren/git-filter-repo仓库中)。 -
将仓库克隆到本地电脑。参见 克隆仓库。
git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY -
进入仓库的工作目录。
cd YOUR-REPOSITORY -
运行
git-filter-repo命令以清理敏感数据。若要在所有分支/标签/引用中删除特定文件,请运行以下命令, 将
PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA替换为要删除的文件在 Git 中的路径,而不仅仅是文件名(例如src/module/phone-numbers.txt)。git-filter-repo --sensitive-data-removal --invert-paths --path PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA重要提示
如果该文件曾在其他路径(因为移动或重命名)出现过,您必须为该路径额外添加
--path参数,或再次运行上述命令并指定替代路径。若要在仓库历史中所有非二进制文件里,将
../passwords.txt中列出的所有文本替换为其他内容,请运行以下命令。git-filter-repo --sensitive-data-removal --replace-text ../passwords.txt -
再次确认已从仓库历史中删除所有想要删除的内容。
-
统计此次历史重写会对多少个 Pull Request 产生负面影响。后续步骤需要此信息。
$ grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs 4可以去掉
-c参数,以查看受影响的 Pull Request。$ grep '^refs/pull/.*/head$' .git/filter-repo/changed-refs refs/pull/589/head refs/pull/602/head refs/pull/604/head refs/pull/605/head输出中第二个和第三个斜杠之间即为 Pull Request 编号。如果受影响的 Pull Request 数量超出预期,您可以直接弃用此克隆(不会产生副作用),然后重新进行重写或放弃此次敏感数据删除。进入下一步后,重写将不可逆。
-
当您对仓库状态满意后,使用强制推送将本地更改覆盖到 GitHub.com 上的仓库。尽管
--mirror已经隐含--force,这里仍显式写出,以提醒您正在强制更新所有分支、标签和引用,并且会丢弃其他人在您清理期间对这些引用所做的任何更改。git push --force --mirror origin此命令会因为 GitHub 将以
refs/pull/开头的引用标记为只读而推送失败。这些失败将在下一节处理。若其他引用也推送失败,可能是对应分支启用了分支保护,需要暂时关闭保护后重新推送。重复此过程,直至唯一的推送失败仅限于以refs/pull/开头的引用。
在 GitHub 上彻底删除数据
使用 git-filter-repo 删除敏感数据并将更改推送至 GitHub 后,仍需执行若干步骤才能在 GitHub 上彻底清除这些数据。
-
通过 GitHub 支持门户 与我们联系,并提供以下信息:
- 受影响的仓库所有者及仓库名称(例如 YOUR-USERNAME/YOUR-REPOSITORY)。
- 上一步统计得到的受影响 Pull Request 数量。支持团队会用此信息确认您已了解受影响范围。
- git-filter-repo 输出中标记的“First Changed Commit(s)”(查找输出中的
NOTE: First Changed Commit(s))。 - 如果 git-filter-repo 输出中出现
NOTE: There were LFS Objects Orphaned by this rewrite(位于 First Changed Commit 之后),请说明您有 LFS 对象孤儿,并将相应文件上传至工单。
当您已成功清除除 PR 之外的所有引用且没有分叉仍持有敏感数据后,支持团队将会:
-
取消引用或删除 GitHub 上的受影响 Pull Request。
-
在服务器上执行垃圾回收,以将敏感数据从存储介质中彻底清除。
-
移除缓存视图。
-
若涉及 LFS 对象,还需删除或清除孤立的 LFS 对象。
重要提示
GitHub 支持不会删除非敏感数据,仅在我们判断仅通过轮换受影响凭证仍无法降低风险的情况下,帮助删除敏感数据。
-
协作者必须变基(而非合并)他们基于您旧(已污染)历史创建的任何分支。一次合并提交可能会重新引入您刚刚努力清除的污染历史。他们可能还需要执行其他步骤;请参见 Make sure other copies are cleaned up: clones of colleagues(位于
git-filter-repo手册中)。
避免将来意外提交
防止贡献者意外提交可以帮助您避免敏感信息泄露。更多信息请参阅 组织内部防止数据泄漏的最佳实践。
以下几点可以帮助您避免提交或推送不应共享的内容:
- 如果敏感数据很可能出现在不应被 Git 跟踪的文件中,请将该文件名添加到
.gitignore(并确保将对.gitignore的修改提交并推送,以保护其他开发者)。 - 避免在代码中硬编码机密。请使用环境变量或 Azure Key Vault、AWS Secrets Manager、HashiCorp Vault 等密钥管理服务,在运行时注入机密。
- 创建 pre-commit 钩子,在提交或推送前检查是否包含敏感数据,或在 pre-commit 钩子中使用如 git-secrets、gitleaks 等成熟工具。(请确保每位协作者都安装并启用您选择的 pre-commit 钩子。)
- 使用 GitHub Desktop 或 gitk 等可视化工具进行提交。可视化工具更容易让您直观看到每次提交会添加、删除或修改哪些文件。
- 避免在命令行中使用笼统的
git add .与git commit -a——改为使用git add 文件名与git rm 文件名单独暂存文件。 - 使用
git add --interactive对每个文件的更改逐一审阅并暂存。 - 使用
git diff --cached检查已暂存的更改。这正是git commit在未使用-a参数时将生成的 diff。 - 为仓库启用推送保护,以检测并阻止包含硬编码机密的推送。详情请参阅 关于推送保护。
延伸阅读
git-filter-repo手册页面,特别是 “DISCUSSION” 部分下的 “Sensitive Data Removal” 小节。- Pro Git:Git 工具 - 重写历史
- 关于机密扫描