拆一行命令:pre-commit = 用 YAML 管理 git hook 的框架;run --all-files = 别只扫 staged 的文件,把仓库里所有追踪文件过一遍;末尾的 gitleaks = 只跑 id 叫 gitleaks 的那个 hook。合起来:手动全量触发密钥扫描器,查有没有人把 API key / 密码 / token 硬编码进代码。 但这里有个反常识的坑,值得从头扒一遍。
合起来:手动全量触发密钥扫描器,查有没有人把 API key / 密码 / token 硬编码进代码。
类比:机场安检。平时 commit 只查你手上拎的包(staged files);--all-files 是让全体乘客把所有行李重新过一遍 X 光。gitleaks 就是那台 X 光机——但它有个反常识的脾气,见下一节。
pre-commit 侧: hook 定义在各上游 repo 的 .pre-commit-hooks.yaml,你的 .pre-commit-config.yaml 用 pinned rev 引用它们。首次运行会把这些 repo clone 进 ~/.cache/pre-commit/,并按 language(golang/python/node…)建隔离环境。gitleaks 是 language: golang,pre-commit 帮你下好二进制。
核心 gotcha(面试就靠这个拉开差距): gitleaks 官方 hook 是这么定义的——
entry: gitleaks git --pre-commit --redact --staged --verbose
language: golang
pass_filenames: false # ← 命门在这
pass_filenames: false 意味着 gitleaks 根本不理会 pre-commit 传给它的文件列表,它自己去读 git 的 staged diff。所以你加 --all-files,对 gitleaks 这个 hook 几乎是空操作——它扫的还是 staged 内容,没 staged 就啥都不扫。pre-commit run --all-files gitleaks 和 pre-commit run gitleaks 对它来说扫描范围一样。想扫全历史/全仓库,得直接 gitleaks git(旧命令 gitleaks detect),不能靠 --all-files。
gitleaks 引擎: Go 写的。规则在 .gitleaks.toml,两把刷子——(1) 正则匹配已知格式(AWS key、GitHub PAT 那种有前缀的);(2) Shannon 熵抓无固定格式的高熵随机 token。遍历 git object/diff,逐行匹配,命中即报,allowlist 降噪。
Q1:默认扫 staged,为啥 CI 里要 --all-files?
因为开发者本地能 --no-verify 跳过 hook,而且 hook 常是后加的,老文件从没被扫过。CI 里 --all-files 给你一条全量基线,不给漏网之鱼。
Q2:那 --all-files 对 gitleaks 到底有没有用?
对官方 gitleaks hook 基本没用(pass_filenames:false + --staged,只看暂存区)。要全量得改 entry 或在 CI 里直接调 gitleaks git。这是高频踩坑点,答对直接加分。
Q3:密钥没固定格式,gitleaks 怎么抓?复杂度? Shannon 熵。对候选 token 算熵,超阈值(规则里可配,常见 3.5–4.5)判可疑。复杂度 O(字节数 × 规则数),正则是主成本;大仓库全历史扫可能几十秒到几分钟。
Q4:密钥已经 commit 进历史了,pre-commit 拦得住吗?
拦不住。--staged 模式只看这一次提交。历史里的密钥 = 已经泄露,gitleaks git 扫得到(它遍历所有 commit 的 diff),但补救只有一条路:rotate 密钥 + git filter-repo 清史。光 git rm 文件没用,旧 commit 里还在。
Q5:误报把全团队 block 了怎么办?
allowlist(正则/路径/commit SHA/stopwords)、行内 # gitleaks:allow、baseline 文件(--baseline-path 记录已知可接受命中)。设计是 fail-closed——宁可误报也不放过真泄露,但必须给 escape hatch,否则团队集体 --no-verify,hook 就成摆设了。
Q6:monorepo 上扫得慢?
gitleaks 内部 goroutine 并行。CI 别每次全历史扫,用 baseline 做增量;或 --log-opts 限定 commit 范围。增量扫 + 全量基线定期跑,是标准姿势。
大厂不靠单个开源工具,玩的是纵深防御(defense in depth):
--no-verify 跳过 → 只是提醒真正的教训不是「扫得更狠」,而是密钥根本不该进 repo:该进 Vault(HashiCorp Vault / AWS Secrets Manager / 1Password)+ 短时 token。gitleaks 是渔网(catch net),不是主控制(primary control)。Uber 2016、Toyota 源码泄露,一堆事故根因都是 credential 进了 history。
机制不变,但合规压力(SOX、PCI-DSS)把 secret scanning 从「nice to have」变成审计硬指标。一个 hardcoded 的 prod DB 密码 = 潜在数千万损失 + 监管处罚,fail-closed 必须真的 fail。
这些环境额外上:签名 commit(GPG / sigstore)、不可变审计日志、密钥 rotation 全自动、以及 canary token——故意埋假密钥,一旦有人拿去用,立刻知道有人在扒你代码/日志。
进一步:1Password CLI 注入(repo 里永远只有 op:// 引用,运行时才解出真值)+ canary,才是从架构上消除问题类(eliminate the problem class)——让密钥根本进不了 repo,而不是靠扫描器事后抓。扫描器抓症状,架构治病因。
gitleaks git / gitleaks dir / gitleaks stdin 取代老的 detect/protect(现为 deprecated alias)。用老命令会显得停在几年前。pre-commit 仍是事实标准,但 Rust 重写的 prek(单二进制、免 Python 环境、更快)2025 起在抬头,契合整个 CLI 工具 Rust 化的浪潮(jaq、polars 那一挂)。想真在 CI 全量扫(而不是被 --all-files 骗),正确姿势是绕开 hook 的 staged 限制:
# .github/workflows/secrets.yml
- name: Full-history gitleaks scan
run: gitleaks git --redact --verbose --exit-code 1
# 而不是指望 `pre-commit run --all-files gitleaks` 扫全历史
git secret 防护和免疫系统是同构的多层防御:
--no-verify 就是划破皮肤);最狠的一点:密钥一旦 commit 进历史,等于病毒整合进了基因组——git history 不可变,你不能只做「表面清除」。必须 rotate(让旧密钥失效 = 抗体让病毒无害)+ filter-repo(基因编辑切除)。只 git rm 文件,就像只擦掉症状,病毒还在染色体里。
gitleaks 抓的是症状,git history 是病历——密钥一旦进史就等于已泄露,--all-files 只给你全量基线,真正的修复永远是 rotate + 清史 + 让密钥从架构上进不了 repo;能被 --no-verify 跳过的 hook 只是提醒,server 端 push protection 才是防线。