1.引言:你删除的提交,并没有完全消失
很多开发者(包括我在内)以为,只要强制推送**(git push -f)** 就能把上一次不小心提交的敏感信息从仓库里"抹掉"。
但现实是:即使提交在分支里消失了,它仍然存在,并且依然可访问。
这种现象意味着:
-
误提交的密钥依旧可能被恢复
-
只要有人知道提交哈希 ,就能找到它
-
这类"消失的提交"可能长期存在,不会被自动删除
换句话说:删除(覆盖)提交 ≠ 删除风险。
一、什么是"被删除的提交"?
当你执行以下操作:
bash
- `git reset --hard HEAD~1`
- `git push --force`
你只是让这个提交在分支历史中变得"不可见",而不是"不可访问"。
Git 的工作机制决定了:
-
提交对象仍然存在于服务器
-
只是没有分支引用它
-
但只要知道哈希值,就可以访问
所以它更像是"隐藏",而非"抹除"。
二、为什么 GitHub 会保留这些提交?
原因可能包括:
-
审计与追踪需求
-
拉取请求(PR)历史
-
分叉网络依赖关系
-
代码托管的内部索引
这些系统让 GitHub 很难(也不愿意)彻底删除提交对象。
结果就是:强推后"消失"的提交仍在后台保留。
这里给个简单的例子吧
1.直接创建仓库并且模仿错误提交密钥
bash
# 初始化仓库
mkdir test-repo && cd test-repo
git init
echo "正常文件" > file1.txt
git add . && git commit -m "第一次提交"
# 不小心提交密钥
echo "SECRET_KEY=abc123" > .env
git add . && git commit -m "添加配置文件"
2.oh no,怎么吧密码提交了,尝试强制推送来删除上一次提交
bash
# 回退到第一次提交
git reset --hard HEAD~1
# 强制推送
git push --force origin main
3.这回总没事了吧,我们尝试通过上一次提交的哈希访问提交
bash
# 即使强制推送后,只要知道提交哈希仍可查看
git show 上一次提交的哈希值
我们记得提交哈希,我们在 GitHub 上在线检查,发现该提交仍然可以被访问 - 9eedfa00983b7269a75d76ec5e008565c2eff(甚至使用四个十六进制数字 9eef访问也足够了)。然而,这次我们收到一条消息,说该提交已被删除或不属于此仓库的任何分支。
三、如何找到这些"消失的提交"?
核心思路是:
通过 Git 或平台日志中记录的"零提交推送事件"来定位被覆盖的提交。
当你执行 git push -f时,如果这次强制推送没有新增提交 ,而仅仅是将分支指针移动到了更旧的提交位置,那么这次推送事件在日志中会呈现一种特殊现象:
-
推送事件存在
-
但提交列表为空
这意味着: 这意味着,本次推送实质上是"丢弃"了原先在分支头部的一些提交,使分支指向了历史中的某个旧节点。而这些被跳过的提交,就是因强推而"消失"的提交。
举个例子帮助定位:
假设某分支原有提交顺序为:
bash
A --- B --- C(分支原头部)
某次强制推送将分支指针从 C 直接移回 A,那么这次推送:
-
在 Git 服务端(如 GitHub/ GitLab)的推送日志中会被记录为一次分支更新;
-
但提交列表中不会包含任何新的提交(因为 C→A 是回退);
-
此时,提交 B 和 C 就成为"被抛弃"的提交,但它们并没有被立即从仓库中删除,而是成为"悬空对象"(dangling commits),仍可通过特定方式找回
如何利用这类日志进行恢复?
-
**查看Git服务端推送日志(Push Logs)**
-
在 GitHub 上,进入仓库 → "Insights" → "Push Logs",查看分支的推送历史。如果某次推送的提交数为0,且之后分支的头部变更为更早的提交,则很可能是一次覆盖性强推。
-
通过日志找到这次推送之前的提交哈希(即被覆盖前的分支头部),即可用于恢复
-
于是,只要找到这类"零提交推送事件",就能定位到被删除的提交哈希。
四、为什么这些提交仍然危险?
因为提交中可能包含:
-
云服务密钥
-
私有令牌
-
数据库连接密码
-
生产环境配置文件
即使提交被"删除",**只要能访问它,就能读取敏感信息**。
并且现实中,很多密钥在长时间内不会被撤销,这就使它们仍然有效。
五、为什么会发生这个情况
当你重置后强制推送时(即 git reset --hard HEAD~1后接 git push --force),你从分支中移除了 Git 对该提交的引用,使其无法通过正常的 Git 导航(如 git log)访问。然而,该提交在 GitHub 上仍然可以访问,因为 GitHub 存储了这些引用日志(reflogs)。
为什么?我不完全确定,但 GitHub 确实给出了一些提示。在我看来,GitHub 远比一个单纯的 git 服务器复杂。它有许多层,包括拉取请求、分支、公私设置等等。
我的猜测是,为了支持所有这些功能,GitHub 会存储所有提交并且从不删除它们。这里有一些需要考虑的情况:
-
拉取请求是什么? 这些只是临时分支,正如 Aqua Security 所写,可以通过使用
git -c "remote.origin.fetch=+refs/*:refs/remotes/origin/*" fetch origin获取所有引用来检索。 -
**GitHub 的分支网络如何工作?** 当你"分支"一个仓库时会发生什么?所有数据都会被复制,包括你可能删除的提交。
对于这些情况,可能还有许多其他原因(审计?监控?),GitHub 会存储所有提交,即使你强制推送头部并"删除"提交,也不会删除它们。
六、如何大规模识别这些风险?
可以归纳成一个流程模型:
-
获取公开事件数据
-
筛选"零提交推送事件 "
-
提取被覆盖的提交哈希
-
访问这些提交内容
-
扫描是否含敏感信息
-
筛选高价值目标
这套流程可以自动化,但核心原理不变。
当然还有更简单的!
claude!我准备推送代码了,帮我检查一下我的代码或者配置中有没有敏感信息
只要在推送前问问你的代码编辑器左边的ai助手一般就不会出现这个问题了