令牌进了历史 —— 删了还是能打出来
目标
把机密文件和大文件真正从历史中移除,并确认移除之后还会残留什么。
为什么重要
git 是内容寻址存储。每个文件都会成为一个 blob 对象,只要还有指向该 blob 的树和提交,对象就一直存活。所以 git rm 只表示“从今以后没有”,并不等于“从未发生过”。机密进入仓库时,如果不懂这个区别,就会在删除并提交之后放下心来,继续使用那个令牌。本实验通过命令输出展示这个区别,并让你熟悉真正清除的顺序(重写、移除备份引用、让 reflog 过期、gc)。最后一步是结论——已经扩散出去的副本里仍然存在,所以对机密来说,撤销比清理更优先。
步骤
- 在
/root/gitx6/repo创建一段混有令牌文件和 900KB 大块数据的历史,并加入在工作树中删除它们的提交。 - 备好同事副本
/root/gitx6/peer,并把当前状态保存到notes/before.txt。 - 用
git filter-branch重写所有 ref。 - 把对象仍然残留的原因写入
notes/original.txt,并清除那个引用。 - 让 reflog 过期,用
gc --prune=now真正删除之后,把体积保存到notes/after.txt。 - 把所有哈希都已改变的事实保存到
notes/rewrite.txt。 - 让
/root/gitx6/peer跟进新历史,并把该副本中是否仍留有机密写入notes/collab.txt。 - 在
notes/report.md中整理事故响应的顺序。
参考
conf/prod.env按下面两行创建。全部是合成的假值,后面的步骤要确认这些内容的 blob 是否已经消失,所以不要改动任何一个字符。这两行是LABHUB_FAKE_TOKEN=deadbeefdeadbeefdeadbeefdeadbeef和DB_PASSWORD=not-a-real-password-1234。- 提交时间请用
GIT_AUTHOR_DATE和GIT_COMMITTER_DATE固定。 git filter-branch会弹出警告并等待 10 秒。在前面加上FILTER_BRANCH_SQUELCH_WARNING=1就能跳过。- 常见错误:做完第 3 步就认为结束了。只要
refs/original/还在,就没有任何一个对象会消失。 - 常见错误:在第 7 步把
peer删掉重新 clone。本实验要观察的是已经扩散出去的副本里会留下什么,所以必须让原有的副本直接跟进。
创建混有令牌和大块数据的历史
在 /root/gitx6/repo 创建一段混有令牌文件和 900KB 大块数据的历史,并加入在工作树中删除它们的提交。
堆叠五个提交——应用文件、conf/prod.env、assets/blob.bin、修改应用、以及 git rm conf/prod.env 提交。中途创建一个 side 分支,并打上带注释的标签 v1。要在后面看到重写会动到所有 ref,就必须有分支和标签。conf/prod.env 的内容必须与实验说明中 ## 참고(韩文,意为“参考”)一节所写的两行完全一致——因为后面的步骤要通过内容来确认那个 blob 是否已经消失。
明明删了,原文还是能取出来
备好同事副本 /root/gitx6/peer,并把当前状态保存到 notes/before.txt。
先用 git clone /root/gitx6/repo /root/gitx6/peer 备好重写之前的副本(第 7 步要用)。然后用 git count-objects -vH 测量体积,在 git rev-list --objects --all 中查看这两个路径是否出现,并把 git cat-file -p <커밋>:conf/prod.env(占位符为提交)能原样取出令牌原文这一点也写进文件。还要记下当前的 HEAD 哈希,第 6 步要做比较。
重写所有 ref
用 git filter-branch 重写所有 ref。
给 --index-filter 传入 git rm -r --cached --ignore-unmatch <경로들>(占位符为路径列表),并同时使用 --prune-empty、--tag-name-filter cat、-- --all。如果只指定一个分支,side 和 v1 就会抓着旧提交残留下来。10 秒警告用 FILTER_BRANCH_SQUELCH_WARNING=1 跳过。
一个都还没有消失
把对象仍然残留的原因写入 notes/original.txt,并清除那个引用。
重写结束了,但 git rev-list --objects --all 里那两个路径依然存在。查看 git for-each-ref refs/original 就能看出原因——filter-branch 为了能够撤销,把原来的 ref 全部做了备份。列出清单,再用 git update-ref -d 逐个删除。
清空 reflog,真正删除
让 reflog 过期,用 gc --prune=now 真正删除之后,把体积保存到 notes/after.txt。
reflog 也是引用——用 git reflog expire --expire=now --expire-unreachable=now --all 清空。然后运行 git gc --prune=now。没有 --prune=now 的话,要等默认宽限期过后才会被删除。把前后两次 git count-objects -vH 并排写出,让人看清体积缩小了多少。
哈希全部变了
把所有哈希都已改变的事实保存到 notes/rewrite.txt。
用 git cat-file -e 在当前仓库中查找第 2 步记下的旧 HEAD 哈希——找不到。提交哈希是连同父提交和树一起计算的,所以只要改动历史前面的一处,后面的全都会变。还要写下标签 v1 现在指向哪个提交。
已经被带走的副本里依然存在
让 /root/gitx6/peer 跟进新历史,并把该副本中是否仍留有机密写入 notes/collab.txt。
在 peer 里执行 git fetch,origin/main 会变成新历史。用 git reset --hard origin/main 让它跟进。然后在那个副本里用 git cat-file -e 查找机密 blob。从中得出的结论就是本实验的要点——写下因此应该先做什么。
整理成事故响应的顺序
在 notes/report.md 中整理事故响应的顺序。
请用带编号的顺序来写。最重要的是什么排在第一位,同时每行说明清理的四个步骤(重写、移除备份引用、让 reflog 过期、gc)为什么是这个顺序。也别漏掉需要向团队通知什么。