提交前防止把密钥写进 Git 仓库

2026-09-26 · 开发

写代码时临时把 token 硬编码进一个测试文件,想着「待会儿删」, 结果一忙就忘了,git add . 加 commit 一起带上去。 这件事的麻烦之处在于:提交之后删掉也没用,历史里那份对象还在。

为什么删了也不算完

Git 保存的是内容对象(blob),只要那个 commit 还在可达范围内, 对应的 blob 就一直存在于 .git 里。 即使你新建一个 commit 把密钥删掉,旧 commit 依然可以通过 SHA 访问到。

force push 也只能改远端分支指针,远端仓库的旧对象不会立刻被回收, 中间还有一个可以被访问的窗口期。如果仓库是公开的, 这个窗口期通常足够被自动化爬虫扫到。

所以正确顺序永远是:先别提交上去,而不是提交之后再想办法清理。

第一道防线:.gitignore 要在最前面

项目初始化第一件事就是写 .gitignore,把可能装密钥的文件先挡掉:

.env
.env.*
!.env.example
*.pem
*.key
secrets/
credentials.*
config.local.*

保留一个 .env.example 作为模板,里面只写变量名和说明, 值留空。这样别人(或者几个月后的自己)知道需要配哪些项,又不会把真实值带进去。

第二道防线:提交前扫一遍暂存区

关键是用 git diff --cached 而不是 git diff—— 前者只看已经 add、准备提交的内容,这才是真正会进仓库的东西。

git diff --cached | grep -nE 'sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{30,}|AKIA[0-9A-Z]{16}|BEGIN [A-Z ]*PRIVATE KEY'

没有输出就是干净的。有输出就说明有问题,这时候 git reset 撤回暂存, 改完再来。

常用的密钥前缀值得记一下,扫的时候直接按前缀匹配,误报少、速度快:

前缀对应服务
sk-各类大模型 / OpenAI 风格 API
ghp_ / github_pat_GitHub 个人访问令牌
AKIAAWS Access Key ID
glpat-GitLab 令牌
xoxb-Slack Bot Token

第三道防线:pre-commit 钩子

靠人记住检查是不可靠的。在 .git/hooks/pre-commit 放一个脚本, 命中就拒绝提交:

#!/bin/sh
# 拒绝提交疑似密钥的内容
if git diff --cached | grep -qE 'sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{30,}|BEGIN [A-Z ]*PRIVATE KEY'; then
    echo "pre-commit: 暂存区里疑似包含密钥,已阻止提交。" >&2
    echo "请先移除相关内容,再重新 add。" >&2
    exit 1
fi

记得 chmod +x .git/hooks/pre-commit,否则不会执行。 被钩子拦住是正常现象,不要用 --no-verify 绕过—— 绕过的恰恰是唯一起作用的那道关。

如果已经提交上去了

处理顺序是「先作废,再清理」,而不是反过来:

  1. 立刻到对应平台把这个密钥吊销 / 轮换,这一步最重要,也最快
  2. 再考虑用 git filter-repo 之类工具重写历史
  3. 重写后通知所有协作者重新克隆,旧 clone 里的历史仍然存在

很多人第一步就去做历史重写,花了一小时清理完,密钥其实早就被扫走了。 作废只要一分钟,先做这个。

小结

三道防线各有各的用:.gitignore 挡住大部分手滑, 提交前 grep 兜住漏网的,钩子保证不依赖人的记性。 三样加起来成本不到十分钟,比事后清理省太多。

← 返回笔记列表