detect-secrets:用审计基线阻断新增凭据
detect-secrets 的核心不是“扫完整段 Git 历史”,而是把当前代码中的候选、启用的插件与过滤器写进 .secrets.baseline,再由人审计存量,让 detect-secrets-hook 阻断后来新增的候选。这种模型很适合存量较大的仓库,但也容易被误用:baseline 一旦被当成“所有命中都安全”,门禁就会变成永久豁免清单。
本文使用 detect-secrets 1.5.0 作为实验基线,wheel 的 SHA256 为 e24e7b9b5a35048c313e983f76c4bd09dad89f045ff059e354f9943bf45aa060。Python 运行时、依赖锁和插件配置同样属于结论的一部分,不能只固定顶层包名。
安装在独立工具环境
不要把扫描器装进系统 Python。项目可以维护一个独立虚拟环境,并由受控任务生成带哈希的依赖锁:
python3 -m venv .tools/detect-secrets
. .tools/detect-secrets/bin/activate
python -m pip install --upgrade pip
python -m pip install \
'detect-secrets==1.5.0' \
--only-binary=:all:
detect-secrets --version生产 CI 应改用 pip install --require-hashes -r tools/detect-secrets-requirements.txt,把 detect-secrets 与传递依赖的摘要一并锁定。首次引入时保存 python --version、pip freeze 和 wheel 来源;插件是会读取源码、可能发起验证请求的 Python 代码,来源边界不能低于普通构建依赖。
baseline 同时保存配置和发现
.secrets.baseline 不只是命中哈希集合。它还记录版本、plugins_used、filters_used、文件位置和审计状态。两台机器若启用不同插件或过滤器,即使扫描同一提交,也可能生成完全不同的结果。
先查看当前版本提供的插件,再以离线模式生成 baseline:
detect-secrets scan --list-all-plugins
detect-secrets scan --no-verify > .secrets.baseline
detect-secrets audit .secrets.baseline--no-verify 禁止插件通过网络确认候选是否有效。默认验证行为可能把候选发送给对应服务,普通开发机和 PR 不应承担这项权限。需要在线确认时,应进入独立的安全响应流程,并优先从凭据平台查询状态。
Baseline 中的 hashed_secret 避免直接保存原值,但文件名、插件类型、行号与审计结论仍能暴露系统结构。它可以进入代码评审,却不应被当成普通无敏感 JSON 随意复制。评审者还要确认 diff 中有没有插件消失、过滤器变宽或结果被批量重置。
用无害字符串验证新增阻断
KeywordDetector 可以识别带有密码语义的赋值。实验字符串应明确声明不可作为凭据,不模仿任何厂商格式:
set -euo pipefail
LAB_DIR="$(mktemp -d)"
git -C "$LAB_DIR" init -q
printf 'mode = "clean"\n' > "$LAB_DIR/app.ini"
git -C "$LAB_DIR" add app.ini
detect-secrets scan --no-verify "$LAB_DIR/app.ini" > "$LAB_DIR/.secrets.baseline"
printf 'password = "EXAMPLE-NOT-A-CREDENTIAL-NEWVALUE987654"\n' \
> "$LAB_DIR/new-config.ini"
set +e
detect-secrets-hook \
--no-verify \
--baseline "$LAB_DIR/.secrets.baseline" \
"$LAB_DIR/new-config.ini" \
> "$LAB_DIR/hook.log" 2>&1
hook_rc=$?
set -e
test "$hook_rc" -ne 0
test -s "$LAB_DIR/hook.log"这项测试验证的是“新候选被 baseline 之外的门禁发现”。如果升级后 fixture 不再命中,先检查 KeywordDetector、过滤器和参数变化,再调整无害 fixture;不要换成形似真实 Token 的值,也不要直接降低整个仓库的门禁标准。
还要准备一个相似但应通过的反例,例如指向环境变量名的配置引用。正例证明检测能力,反例约束误报;只保留正例会诱使团队用越来越宽的过滤器解决噪声。
插件与过滤器是两种责任
插件负责产生候选。固定格式 Detector 适合已知厂商令牌,Base64 与 Hex 高熵插件覆盖未知随机字符串,KeywordDetector 关注密码、secret、token 等上下文,PrivateKeyDetector 关注私钥结构。启用哪些插件应由仓库数据类型和威胁模型决定,而不是照抄另一支团队的 baseline。
过滤器在候选产生后排除结果。文件过滤、行过滤、启发式过滤和验证策略都可能减少噪声,也可能制造盲区。--exclude-files 排除整个目录的风险远高于一条局部 allowlist;--exclude-lines 的正则变宽后,可能同时吞掉相邻的真实凭据。
自定义插件与过滤器通过 Python 导入执行,拥有扫描进程的文件读取和网络能力。代码要版本锁定、评审并有 fixture,CI 不应从未经固定的分支动态加载。过滤器变更必须对旧 baseline 运行差异审计,不能只看最终候选数量下降。
audit 是处置工作台,不是消警按钮
detect-secrets audit .secrets.baseline 让审计者把候选标为真实或误报。真实候选要进入撤销、轮换和影响调查;误报要说明为何不可能成为秘密,并考虑用更窄的规则修复根因。对边界不清的结果,不应为了完成审计而强行标成误报。
团队可以查看审计统计与报告,但输出可能重新暴露候选上下文:
detect-secrets audit --stats .secrets.baseline
detect-secrets audit --report --only-real .secrets.baseline报告命令只在受限环境执行,产物进入安全制品库。两人复核适合高权限凭据、批量 baseline 更新和过滤器迁移;普通无害 fixture 则可以由自动测试验证。审计状态不是永久事实,凭据用途、文件内容和插件能力变化后要重新判断。
更新 baseline 时审查设置差异
基于现有 baseline 重新扫描可以保留已标注状态并更新结果:
detect-secrets scan \
--no-verify \
--baseline .secrets.baseline
git diff -- .secrets.baseline评审 diff 时先看版本、插件和过滤器,再看新增与删除结果。若大量记录消失,原因可能是文件删除,也可能是插件被关闭、过滤器扩大或 baseline 格式迁移失败。直接用一份全新扫描覆盖旧文件,会丢掉人工审计和存量处置状态。
# pragma: allowlist secret 适合局部、明显无害且能在代码评审中理解的值。它不是跳过扫描的快捷键:注释应和 fixture 放在一起,并说明为什么不可能用于认证。生成代码、第三方样本和锁文件需要排除时,也要用专门正反样例证明边界没有覆盖自有源码。
本机 Hook 与 CI 的分工
提交前只扫描 staged 文件,反馈快且不会反复遍历仓库:
git diff --staged --name-only -z \
| xargs -0 detect-secrets-hook \
--no-verify \
--baseline .secrets.baseline文件名管道要保留 NUL 分隔,避免空格和换行造成漏扫。没有 staged 文件时要确认 xargs 的平台行为,CI 包装脚本不能意外让 Hook 等待标准输入。开发者可以绕过本机 Hook,因此服务端 PR 必须对同一 diff 复核;默认分支还应定期对所有 tracked 文件扫描。
detect-secrets 并不以完整 Git 历史发现为核心。要追查已经删除的历史泄漏,应使用 Gitleaks、TruffleHog 或仓库托管平台的历史能力,再把处置结果回到凭据响应流程。三个产品可以编排在同一安全流水线,但不应共享一份模糊配置或一个被吞掉的总退出码。
真实泄漏先撤销和轮换
从 baseline 删除记录、从文件删除字符串、给某行加 allowlist,都不会使已经暴露的凭据恢复安全。真正的响应顺序是撤销或禁用、签发替代凭据、迁移消费者、失效旧会话、查询访问日志、清理历史与缓存,最后复扫。
Baseline 记录可作为调查线索,却不能代替凭据平台的状态证据。应用 owner 负责确认消费者和轮换窗口,安全团队负责事件分级与审计,平台团队负责 Hook 分发、Python 环境和制品权限。所有者缺失时,候选不能仅因为等待过久而自动转成误报。
故障、升级与回滚
本机命中而 CI 不命中,先比较 detect-secrets 版本、Python 版本、baseline 内容、插件与过滤器列表、工作目录和传入文件集合。Hook 返回 0 但应命中的 fixture 没有出现,检查输入文件是否真的传入,不能只重跑命令。Baseline diff 突然很大,先排查设置变化,再考虑候选本身。
升级要保存旧版 baseline 与 audit 状态,用同一正反 fixture 跑新旧版本,并比较插件名称、过滤器路径、哈希结果和 Hook 退出码。回滚应恢复上一版 Python 锁、baseline 和 Hook 配置;重新生成 baseline 或关闭 KeywordDetector 不叫回滚。
实验目录清理前确认路径位于系统临时目录,并包含本流程创建的无害文件。真实 baseline、审计报告和 Hook 日志按敏感制品管理;通用构建清理不能递归删除尚未归档的调查证据。
