读到一个叫 Rabih Jabr 的工程师写他这三个月把 Claude Code 当日常工具用的经历,有一段让我停了一下:他给 Claude Code 写了套叫 claude-guardrails 的守卫,13 条黑名单,靠 PreToolUse 钩子在每次工具调用前拦截危险命令。这套机制最耐人寻味的不是它拦住了什么,而是守卫返回的 reason 字符串——那个给人类看的拒绝理由——会被 agent 自己读回去。他看到 agent 被拦之后没有硬闯,而是顺着理由换了一条路走,比如想直接改 package-lock.json 被拒,转而真的去跑 pnpm add 了。
这在人类的安全体系里没有对应物。防火墙的拒绝理由永远不会成为攻击者的改进依据;WAF 的拦截日志也不会被恶意流量拿回去当教学材料。但 agent 不一样,它的接口本来就是文本,拒绝理由是它输入上下文的一部分,于是守卫在"禁止"之外多了一个完全没经过设计的副作用:它在教。一个黑名单工具,拒绝一次,等于给 agent 上了一课,而且这一课真的能被听进去。这个回路太新了,新到让所有关于"AI 安全边界"的既有讨论都不太套得上——我第一反应也是把 claude-guardrails 当成一个笨拙的早期防线看,可它跟传统的黑名单有个本质差别:它面对的执行者,恰好听得懂人话。
把镜头拉远一点看。Rabih 在文章里坦白了他真正怕的是什么:不是 agent 鲁莽,而是它看不见自己行为的爆炸半径。一次本地看起来完全正确的 rebase 修复,推上去可能把整个主分支的历史搅乱;读取一个 .env 文件,凭据未必落盘,但可能已经进了对话记录,从此跟着 context 飘到任何一次未来的工具调用里。他恐惧的其实不是"AI 做了坏事",而是"AI 做了在它那个局部视角里完全合理、但在全局尺度上不可逆的事"。这个恐惧非常准确,准确到值得停下来看一秒——他后续的应对策略,恰恰是这份准确的镜像。
他的解是写一份禁止清单,短到可以写在餐巾纸上:不要内联凭据、不要在 rebase 卡住时强推主分支、不要在没有 BUILD_DIR 时执行 rm -rf、不要手改 package-lock.json、不要让失败的测试静默跳过、不要运行 cat .env。然后他把这些人类社会的规训手段,翻译成了机器能执行的黑名单。
这里藏着整个事件最值得拆的结构问题。
黑名单只能落在表达层,而 agent 的危险行为发生在语义层。把这两件事区分开,是理解 claude-guardrails 能做什么、不能做什么的前提。文本层能拦的是那些表达得足够直白的命令——cat .env、rm -rf、git push --force,这类东西的文本特征清晰,正则一匹配一个准。但 agent 的破坏力从来不在单条命令的文本形态里,而在那条命令被展开之后跟系统状态的交互里。他评论区里有个同行点破过一层:如果环境变量 BUILD_DIR 的值恰好是 /,一条文本上完全合法的 rm -rf "$BUILD_DIR" 展开之后炸的是整个根目录;守卫拦得住文本,拦不住变量展开之后的语义。黑名单这玩意,本质上是在用一个有限的正则集合去逼近一个语义无限的行为空间,它从诞生的那一刻起就是漏的。
这个剧本不是第一次上演。Web 安全的早期,所有人都在做输入过滤,正则黑名单层层加厚,从拦截单引号到拦截 union select,再到各种编码绕过、大小写绕过、注释符绕过,过滤规则集厚到变成一门手艺。后来这个行业花了差不多十年才真正学会一件事:注入问题的解不在输入过滤里,在参数化查询里——把数据和代码分开,让数据库自己处理边界。边界应该待在它该待的地方,而不是被翻译成一堆越来越厚的文本模式放在前面。WAF 到今天还活着,但它早就不是防线了,它只是快速反馈层,真正的安全边界在数据库角色、在文件系统权限、在服务器端的分支保护里。
现在 agent 安全正在逐帧重播这一段路径。Rabih 自己在评论区里认得很干脆——有人列了五种绕过:别名、函数、sh -c、Python 子进程、编码载荷,他逐一确认,全都能穿透他的守卫。他承认这个项目"是一层,不是边界",甚至承认自己文章里没把这话说清楚。这不是他的失误,这是整个阶段的结构性天真:我们还没学会给 agent 设边界,于是本能地用手边最像边界的东西——黑名单——去扮边界。
但 claude-guardrails 的教具属性,倒是给了一个关于护栏去向的线索。
注意看 Rabih 定的那三条守卫规则:守卫失败时必须 fail-open,不能阻塞工具调用;守卫只能投反对票,不能投赞成票;精确性优先于召回率。这些规则如果翻译成人类社会的语言,是一个人在说:我不知道什么是对的,我只知道我经历过什么是不该发生的,而且我不愿意用没见过的事去定义规则。他停在第 13 个守卫没有继续加下去,因为后面四个候选隐患他只有观点、没有真实事故经历——不想用猜测去污染项目的可信度。这种克制在现在的 AI 工具圈子里难得有点反常,大多数同类项目都在拼命扩张规则集,仿佛规则的厚度等于安全度;他反而明白黑名单的可信度建立在"每条规则都见过一次真实事故"上面。没有事故支撑的猜测,写进黑名单里就是噪音,而且是最危险的那种噪音——会让人误以为某个方向已经有人看过了。
到这里可以把整件事放回棋盘上看一层。模型厂商这几季度在 agent 上的竞争,正在从模型能力迁移到执行环境;谁能提供可信的沙盒、权限边界和审计,谁就占住了场景层的护城河位置。Rabih 遇到的问题——agent 看不自己的爆炸半径、单条命令的文本无法表达跨调用的危险序列、守卫需要的状态持久化他还没有解决方案——全部指向同一个结构结论:约束必须下沉到 agent 的运行时权限模型里,不能挂在表达层当插件。他评论区的另一位同行说得更直白:同样的端点,退款 40 英镑和退款 4 万英镑在文本上完全一样,检查必须放在效果实现的系统端,而不是意图表达的前端。Agent 的安全边界最终一定长在权限系统上——文件系统 ACL、数据库角色、网络策略、可审计的执行日志——不长在命令文本的匹配规则上。
这不是说 claude-guardrails 这类工具没有价值。它真正的价值恰恰不在防守,而在教学。被拦住的 agent 会读拒绝理由,会修正自己的下一步动作,这个回路目前只有表达层守卫能提供——权限模型不会教 agent 怎么用 pnpm,拒绝也不会带解释。于是将来的分层会变得清晰:权限层负责真正的边界,黑名单式的文本守卫退化成快速反馈环和教学信道。前端负责上课,后端负责守门;前端可以漏,后端不能漏。这个分层现在还没定型,模厂商也还没把权限模型做成运行时原语,但方向上我看不出有第二条路——除非整个行业愿意把 WAF 时代的剧本完整重演一遍,把黑名单一层层堆到没人维护得动为止。
压个注:未来两年内,主流 agent 框架会把文件访问、网络调用和凭据读取做成权限层原生的、运行时可感知的对象,而不是留给你自己去写 PreToolUse 钩子拦截。护栏工具不会死,但它会从"防线"降级成"教学层"——跟 linter 一样,拦不拦得住已经不是它的核心职责,让执行者理解规则才是。这个判断什么时候该扔掉:如果一年后主流 agent 运行时的权限钩子仍然空着,大家还在靠文本黑名单互相比谁的正则更厚,那我上面说的这一整套层位迁移就没有发生,我们大概率还在重演 2005 年的 WAF 困局,只是换了个主角。
