← 返回

给沙箱加密钥中毒检测:该拦在哪一层

2026-08 · 写 Pi-Anvil 时踩到的一个设计问题:让编码助手在沙箱里干活,它的产出怎么敢往宿主上落

问题从哪来

这个工具的用法是:把编码任务丢给沙箱里的 agent,改动以 diff 回来,人看过再决定应用。 听起来很安全——反正有人审。但"人审"只覆盖被显式列出的那些改动, 而沙箱里还有一类东西是不会被逐行审的:缓存

依赖缓存、构建缓存、跨任务复用的工作区缓存,都是"上一次跑出来的结果,下一次直接信了"。 一旦有内容被污染,它会绕过人的视线,一路活到后面每一次任务里。

两种污染,处理方式完全不同

第一种是内容里带凭据。agent 读了外部仓库、日志、报错文本,里面可能有别人的密钥; 如果这些内容被写进共享缓存,就等于把一份别人的凭据长期留在了我的机器上,还会随缓存复用扩散到其他任务。

第二种是缓存本身被做手脚:共享目录如果被改成世界可写、或者属主不对, 那里面放什么都有可能被下一次任务当成可信输入。

这两类的处置不能一样:前者要拦写入,后者要直接重建,而不是试图修复

最后落成的样子

凭据检测放在缓存写入之前,规则刻意保守——只认形态非常明确的东西:

const SECRET_PATTERNS = [
  /AKIA[0-9A-Z]{16}/,                    // AWS access key id
  /-----BEGIN [A-Z ]*PRIVATE KEY-----/,  // 私钥头
  /ghp_[0-9A-Za-z]{36}/,                  // GitHub 传统 PAT
];

外加一条按文件名直接判定的规则:.env.env.* 一律算命中,不看内容。

几个当时纠结过、最后定了的取舍:

为什么放在写入而不是读取

直觉上"读的时候检查"更省事。但读取点检查只能挡住这一次读取,同一份脏内容还在缓存里, 下一个读取路径、下一个消费者未必走这段代码。写在入口拦一次,后面所有消费者都自动安全; 读的时候拦,就得要求每个消费者都记得拦。

代价是写入点检查会误伤:合法的测试夹具里也可能有 ghp_ 开头的假 token。 所以这套规则只用来"标记 + 拒绝进入共享缓存",不会让任务失败——被标记的内容留在任务私有目录里, 人想看还是能看到。

还没解决的

形态检测天然漏报:换一种前缀、base64 套一层、或者干脆是模型自己生成出来的凭据,都不在规则里。 真正彻底的方案是缓存里只放内容哈希、不放原文,但那会牺牲掉跨任务复用的收益。 现在的定位就是"便宜的第一道",不是答案。