安全指南

用 gitleaks 在提交前拦下密钥:在 push 之前堵住 API 密钥泄露

把 API 密钥不小心提交后,它会留在 Git 历史里,等于永久泄露。本文讲解如何用 pre-commit 钩子和 CI 两处检查让 gitleaks 拦住密钥,以及光靠检出无法完成、必须另外做的吊销步骤。

发布于 2026-06-12 更新于 2026-10-06 6 分钟阅读
本页目录

适用对象:在个人开发或小团队中使用 Git、担心「会不会不小心把 API 密钥提交上去」的人。本文不讲攻击方的手法,只讲阻止机密从自己的仓库流出的机制。

本站的看法:光靠 .gitignore 守不住机密

常听到有人说「我把 .env 放进 .gitignore 了,所以没问题」,但这会漏掉一些情况。.gitignore 只是不让新文件被追踪,对已经提交的机密、赶时间用 git add -f 强制添加的机密、以及 .env.local.bak 这种意料之外的文件名都抓不住。它只能防住机密混入的部分途径。还需要一个真正查看提交内容、标记「看起来像机密的字符串」的东西,也就是检测器。这正是 gitleaks 的用途。

为什么要在「提交前」拦住

机密之所以危险,是因为 Git 会永久保留历史。即使从最新文件里删掉,过去的提交里还留着。而在 push 的那一刻,这段历史就会被复制到其他机器、服务器和 CI 日志里。

留在历史
从最新删掉也会残留在过去的提交里
push=扩散
复制到其他机器、服务器、CI 日志
必须吊销
泄露过一次的密钥只能重新签发
两处检查
在 pre-commit 和 CI 拦住

所以机密防护的重点其实是泄露前的预防,而不是泄露后的补救。这和「别把机密留在公开目录里」的盘点(→ 公开目录的盘点)是同一个思路,只是用在了代码上。

在哪些环节检出机密

在机密从代码流向外界的路径上,可以设两处检查:本地提交的时候,以及被共享之前(CI)。

代码里的机密

API 密钥、私钥、token

→

检查 1:pre-commit

提交时在本地检出并中断

→

检查 2:CI / cron

push 后在合并/部署前检出

→

公开/共享

到这一步就要按已泄露处理

机密的流出路径与两处检查。pre-commit 在本地拦,CI 在合并/部署前拦。

如何集成 gitleaks

1

先扫描现有历史

引入时先用 gitleaks git(v8.19.0 之前的版本为 gitleaks detect)把整个仓库的历史扫描一次。找出过去提交里留下的机密是第一件事。这里找到的东西,如下文所述,都要以吊销为前提。
2

用 pre-commit 钩子加上检查 1

每次提交都运行 gitleaks git --pre-commit --staged(以前是 gitleaks protect --staged),当场中断包含机密的提交。把它加进 pre-commit 框架(.pre-commit-config.yaml),所有人在本地就会运行同样的检查。
3

用 CI / cron 加上检查 2

钩子可以被每个人自行禁用,所以要在共享之前再检查一次。在 CI 的 job 中运行 gitleaks git,自建环境则作为定期扫描运行,抓住漏掉的东西(和依赖审计一样的自动监控思路)。
4

在 .gitleaks.toml 中调整误报

测试用的假密钥或示例值被命中时,在 .gitleaks.toml 中调整规则和 allowlist。正确的做法不是「压掉警告」,而是「记录为什么它是安全的」,没有依据的排除可能引发下一次事故。

检出后先吊销,改写历史在后

发现已被提交并 push 的机密时,最优先的是吊销并重新签发(轮换)那个密钥或 token。用 git filter-repo 从历史中删除固然重要,但那只是「不让它继续扩散」的善后。要假定已经到达公开仓库、fork、CI 日志或他人 clone 的机密无法收回:先让它失效,再清理历史。顺序反了,你还在删除的时候它就可能已经被人使用了。

只靠 .gitignore

  • 只能防止新的追踪
  • 已经提交的机密检不出来
  • 对 git add -f 或奇怪的文件名无能为力
  • 无法验证「我以为没加进去」

gitleaks(检出+吊销流程)

  • 实际检查工作树和历史的内容
  • 用 pre-commit 和 CI 两处检查拦住
  • 检出后有一套直到吊销为止的流程
  • 误报记录依据后再排除

该用哪个命令(v8.19.0 及以后)

gitleaks 在 v8.19.0 中重新整理了命令。旧的 detect 和 protect 仍能运行,但已被弃用,也不再出现在帮助中。新配置时请使用下面这些:

命令扫描对象典型用途
gitleaks gitGit 提交历史首次全历史扫描、CI、定期扫描
gitleaks git --pre-commit --staged即将提交的更改(已暂存)pre-commit 钩子(官方钩子运行的正是这条)
gitleaks dir文件夹和文件,包括不在 Git 中的公开网页目录、备份、解压后的发布 zip
gitleaks stdin通过标准输入传入的文本其他命令的输出或日志

gitleaks 发现至少一处泄露时以退出码 1 结束,没有发现时为 0,所以 CI 和 cron 任务可以根据退出码判定失败。加上 --redact,机密本身就不会出现在输出或日志里。

配置示例:pre-commit 钩子与 GitHub Actions

使用 pre-commit 框架时,把下面的文件放在仓库根目录。添加时把 rev 设为最新版本(截至 2026 年 10 月为 v8.30.1)。

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks

除了 gitleaks(用 Go 构建)之外,id 还可以是 gitleaks-docker(在 Docker 中运行)或 gitleaks-system(使用机器上已安装的 gitleaks)。然后每个人在自己的机器上运行:

pip install pre-commit        # install pre-commit itself
pre-commit install            # register the hook in this repo (once per clone)
pre-commit run --all-files    # check every file once, right after registering
pre-commit autoupdate         # run now and then to move rev to a newer release

在 GitHub 上做 CI,可以使用官方的 GitHub Action。它需要完整的历史,所以用 fetch-depth: 0 进行 checkout。

# .github/workflows/gitleaks.yml
name: gitleaks
on:
  pull_request:
  push:
  schedule:
    - cron: "0 4 * * *"
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v3
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}

对于组织账户拥有的仓库,这个 Action 需要许可证密钥(GITLEAKS_LICENSE)。官方 README 说明该密钥可以从 gitleaks.io 免费获取,个人账户不需要。在自建 Git 服务器或 GitHub 以外的 CI 上,在 job 中运行 gitleaks git --redact 也能达到同样效果。

在哪里检查:四个位置的比较

任何单独一处检查都会留下漏洞。把每一处能拦住什么、在哪里会漏整理出来,就能看出为什么要组合使用。

位置拦截时机覆盖范围漏洞和限制
pre-commit 钩子(本地)提交之前已暂存的更改用 git commit --no-verify 或 SKIP=gitleaks 就会跳过。clone 仓库不会安装钩子
CI(GitHub Actions 等)合并或部署之前已推送的提交和历史push 已经发生。公开仓库的话要按已泄露处理
定期扫描(cron)事后发现整个仓库的历史发现有延迟。能抓到新规则在旧提交中发现的东西
GitHub push protection推送到 GitHub 时GitHub 支持的机密类型给出理由就能绕过。对其他 Git 服务器不起作用

面向用户的 GitHub push protection 对推送到公开仓库默认开启。它是免费的安全网,但拦不住本地提交,不支持格式的密钥或手工拼出的连接字符串也可能漏过(详见 为什么在 GitHub 上暴露的凭据仍然有效)。gitleaks 覆盖的是它之前的那一步,以及 GitHub 以外的一切。

如何检查自己的环境

配置过一次,不代表它在运行。在自己的仓库里按顺序检查以下几点。

1

确认 gitleaks 的版本

运行 gitleaks version。早于 v8.19.0 的版本没有 gitleaks git 等新命令,需要更新。

2

确认钩子已注册

查看是否存在 .git/hooks/pre-commit。如果 git config --get core.hooksPath 指向其他文件夹,就去那里查看。clone 时 Git 不会复制钩子,所以在新机器或新的 clone 上要再次运行 pre-commit install。

3

把整个历史扫描一次

在仓库根目录运行 gitleaks git --redact -v。没有检出时退出码为 0。如果有检出,根据输出中的文件名和提交,到签发方的控制台确认该密钥是否仍然有效。

4

也扫描 Git 以外的文件夹

服务器上的公开目录、备份以及要分发的 zip 的内容,在 Git 历史扫描中都看不到。用 gitleaks dir (path to folder) --redact 检查它们(→ 公开目录的盘点)。

5

确认 .env 是否被提交过

运行 git log --all --oneline -- .env,查看过去是否有提交涉及 .env。只要出现一行,那个时间点的内容就在历史里。

误报的处理

测试用的假密钥和文档中的示例经常会被标记。排除的方法有三种,使用能解决问题的最窄的一种。

  • 按行:在那一行加上 gitleaks:allow 注释
  • 按检出结果:把输出中显示的指纹加进仓库根目录的 .gitleaksignore(README 将其标为实验性功能)
  • 按文件或模式:加进 .gitleaks.toml 的 allowlist

如果自己编写 .gitleaks.toml,在 [extend] 下写上 useDefault = true 以保留默认规则。只有自定义规则的配置,会悄悄漏掉默认规则本来能抓到的密钥。

如果旧仓库里已有大量检出结果,可以把过去的报告传给 --baseline-path,只报告新的检出。但要先吊销那份报告里的密钥。把密钥放进 baseline 并不会让它变安全。

常见错误及修正方法

  • 以为有配置文件就够了:在有人运行 pre-commit install 之前,钩子什么也不做。让每个人都运行,并在 CI 中运行同样的检查作为第二层。
  • 习惯了使用 --no-verify:把 CI 检查设为合并的必要条件,这样被跳过的钩子也能被抓住。
  • 把机密打印到 CI 日志里:加上 --redact。CI 日志也是很多人都能看到的。
  • 在 CI 中扫描浅克隆:只会检查最新的提交,历史中的机密会被漏掉。设置 fetch-depth: 0。
  • 从历史中删除后就结束:GitHub 自己的指南把吊销或轮换机密作为第一步。fork、clone 和缓存中的副本无法删除。
  • 把真实的密钥当作「误报」加进 allowlist:在注释中写明理由,并在评审时检查。

真实案例:访问密钥被公开放置

2022 年 10 月 7 日,丰田汽车公司宣布,其联网汽车服务 T-Connect 用户网站的部分源代码曾在 GitHub 上公开。根据该公司的公告:

  • 2017 年 12 月,负责开发该网站的委托方误将部分源代码上传到设置为公开的 GitHub 账户
  • 代码中包含存储客户信息的数据服务器的访问密钥
  • 代码于 2022 年 9 月 15 日改为非公开,访问密钥于 9 月 17 日更换
  • 涉及 296,019 个电子邮件地址和客户管理编号。访问日志中没有发现第三方使用的痕迹,但公司表示无法完全排除这种可能

从上传到删除,大约过了五年。而且这发生在公司自己的仓库之外,在委托方的账户里。对自己仓库的扫描无法覆盖代码最终可能去的所有地方,所以还要限制每把密钥能做的事,并定期更换密钥。即使在这个案例中,密钥也是在代码下架两天后才更换的:发现之后,就要更换密钥。

私有仓库也不是存放机密的安全地方。在 CAMPFIRE 2026 年的案例中,据该公司称,被滥用的 GitHub 凭据是起点,在 GitHub 上可以查看的信息被用来获取云服务凭据(→ CAMPFIRE 入侵事件(英文))。私有仓库中也不要放机密,并定期用 gitleaks 扫描。

本站是怎么做的

本站的基本规则是机密一概不放进 Git:连接字符串和 API 密钥只放在服务器上受保护的配置里(权限受限的 env 文件),仓库和交接资料中都不留明文(→ 不把机密放进 git/自建 Git vs GitHub)。在此之上,再以人一定会犯错为前提叠加检测器。用设计保证「不放进去」,用 gitleaks 这类扫描抓住「还是放进去了」,这两层就是本站防止机密泄露的做法。处理机密的原则本身,与 .env 文件与机密 以及 安全保存密码 相同。

接下来阅读

参考资料

  • gitleaks(官方仓库)README:命令(git、dir、stdin)、v8.19.0 中 detect/protect 的弃用、退出码、--redact、gitleaks:allow、.gitleaksignore、--baseline-path、配置扩展 — github.com/gitleaks/gitleaks / 官方钩子定义 — .pre-commit-hooks.yaml / Releases
  • gitleaks-action(官方 GitHub Action)README:工作流示例与许可证密钥 — github.com/gitleaks/gitleaks-action
  • pre-commit 文档(install、run --all-files、autoupdate、SKIP) — pre-commit.com
  • Git 文档「githooks」(pre-commit 钩子与 --no-verify) — git-scm.com
  • GitHub Docs「About push protection」 — docs.github.com / 「Removing sensitive data from a repository」 — docs.github.com
  • 丰田汽车公司,关于客户电子邮件地址可能泄露的通知(2022 年 10 月 7 日,日文) — global.toyota

FAQ

Qgitleaks 是什么?
A

一款免费工具,扫描 Git 仓库的工作树和提交历史,检出是否有 API 密钥、私钥、token 等机密被提交。它通过正则规则和字符串的熵(随机度)找出『看起来像机密的字符串』,可以作为 pre-commit 钩子或在 CI 中运行,进行自动检查。

Q只要写进 .gitignore 就能守住密钥吗?
A

不够。.gitignore 只阻止『新的追踪』,对已经提交的机密、或用 git add -f 强制添加的机密都检不出来。它只能防止一部分事故,所以要在 pre-commit 和 CI 两处配合 gitleaks 这样的检测器。

Q不小心把密钥提交并 push 了怎么办?
A

要把那个密钥当作已经泄露。最优先的不是改写历史,而是吊销并轮换(重新签发)暴露的密钥或 token。要假定进入过公开仓库或日志的机密无法收回:先让它失效,再清理历史并加上防范措施(gitleaks)。

Qgitleaks detect 和 gitleaks protect 还能用吗?
A

仍然可以运行,但它们在 v8.19.0 中已被弃用,也不再出现在帮助输出中。现在的命令是 gitleaks git(扫描提交历史)、gitleaks dir(扫描文件夹和文件)和 gitleaks stdin(扫描通过管道传入的内容)。提交前的检查现在是 gitleaks git --pre-commit --staged,官方 pre-commit 钩子运行的也是这条命令。新配置时请使用新命令。

Q开启了 GitHub push protection,还需要 gitleaks 吗?
A

两者覆盖的范围不同,所以都要用。面向用户的 GitHub push protection 对推送到公开仓库默认开启,但选择一个理由就可以绕过,而且对本地提交和 GitHub 以外的 Git 服务器不起作用。gitleaks 在提交之前就在你的机器上拦住机密,也能在自建 Git 服务器或任何 CI 上运行同样的规则。

Q怎样让 gitleaks 不再标记测试用的假密钥?
A

有三种方法:在那一行加上 gitleaks:allow 注释;把该检出结果的指纹写进 .gitleaksignore;或者在 .gitleaks.toml 的 allowlist 中排除文件或模式。无论用哪种,都要留下注释说明为什么是安全的。最需要避免的失误,是把真实的密钥当作误报加进 allowlist。