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 套一层、或者干脆是模型自己生成出来的凭据,都不在规则里。 真正彻底的方案是缓存里只放内容哈希、不放原文,但那会牺牲掉跨任务复用的收益。 现在的定位就是"便宜的第一道",不是答案。