已知的假密钥,标记一次就够了

只要你让 agent 写过 AWS 示例代码,会话记录里多半就有 AKIAIOSFODNN7EXAMPLE。这是 AWS 官方文档里的示例密钥,完全符合 AWS 的格式,于是 spillscan 每次都会报它。测试里的 fixture token、从教程里粘过来的 JWT 也一样。同一条无害的结果看到第三遍,人就开始草草扫过报告。总是误报的警报,最后只会让人学会无视它。

所以 spillscan 现在会读取一份白名单。关键在于名单里写什么。

写指纹,不写值

每条结果现在多了一列 FP:对值做 SHA-256(混入 spillscan 专用前缀)后取前 12 位十六进制。它能认出这个值,但不包含这个值。

$ spillscan scan
RULE        PREVIEW    FP            AGENT        FILE:LINE
aws-akia    AKIA…MPLE  8cc03843ecd0  claude-code  ~/.claude/projects/…/x.jsonl:128
github-pat  ghp_…a91F  1d6e40b2c9f7  claude-code  ~/.claude/projects/…/x.jsonl:128
...
  Known-fake value? Add the line fp:<FP> to ~/.config/spillscan/allow.txt; scan and redact then skip it.

把认得出的那一个抄进 ~/.config/spillscan/allow.txt:

fp:8cc03843ecd0   # AWS 文档示例密钥

为什么不让人直接写值?因为白名单恰恰是会被提交进 dotfiles、发给同事、贴进 issue 的那个文件。如果里面存的是值,哪天有人把一个以为是假的密钥加进去,白名单本身就成了新的泄露点。存指纹的话,最坏也只是一段 12 位的哈希。另有 value: 用来写那些本来就是假的字面值,文档里也写明了它会原样保存你输入的内容。

也可以按规则和位置

有时候噪音是一整类:比如你做认证教程那几个会话里的所有 JWT。同一行的多个条件必须同时满足,不同行之间是“或”。

rule:jwt path:~/.claude/projects/*auth-tutorial*/**
path:~/.codex/sessions/2026/01/**
rule:generic-assign

glob 匹配的是会话文件的绝对路径:* 不跨目录,** 可以跨,~/ 是家目录,不含斜杠的 glob 只匹配文件名。写错了,比如 rule:jtw,会直接报错退出:allow.txt:1: unknown rule "jtw" (did you mean rule:jwt?)。一份悄悄什么都匹配不上的白名单,比没有更糟。

藏起来的东西,都留了记录

隐藏结果是这个功能里有风险的那一半,所以 spillscan 会明说:表头写着 allowlisted=1,末尾一行注明是哪份名单藏的,spillscan scan --no-allow 能看到原始读数。被白名单命中的结果不会导致退出码 2,CI 检查可以通过,但 --json 里仍然有这个计数。redact 对这些值原样保留,并报告保留了几个。MCP 的 scan_report 遵循默认白名单,而且依然只读。

额外的名单用 --allow FILE 传入,可以重复。scan 给出的后续命令会带上它们,所以你照抄的那条清洗命令,跳过的正好是这次扫描隐藏的那些。

测试抓到的一个 bug

PEM 私钥跨多行。在 JSONL 文件里,这些换行存成 \n 转义;JSON 解码之后则是真正的换行。scan 对原始字节算指纹,redact 遍历的是解码后的 JSON,于是同一把密钥得到两个不同的指纹,加进白名单的 PEM 块照样会被清洗。现在 redact 两种形式都会检查,而且有一个测试专门确保去掉这个检查就会失败。

该功能已在 main 上,会随下一个版本发布。格式细节见 Configuration;源码与 README。最后提醒一句:只有确认是假的才该加白名单。真泄露的密钥,照样要去轮换。

— 2026-10-28