文档导航

什么必须写在代码里

置信度门控那一页讲的是「阈值交给代码」。这一页讲得更强一层: 有一批检查根本不该问模型。

最强的一句边界声明

公开的做法里最硬的一条来自一个 agent 循环的实现:

风险分高的工具调用强制执行人工授权,任何概率都不能覆盖它。

「任何概率都不能覆盖」这句话值得单独拿出来。它说的不是「置信度要很高」, 而是这条规则不在概率空间里。区分这两者很重要:

  • 「≥ 0.95 才自动放行」是一条阈值规则 —— 理论上存在一个足够高的概率能让它通过。
  • 「高风险必须人工批准」是一条结构性规则 —— 无论模型说什么都成立。

两者的可靠性不在一个量级上。你的系统里应该明确区分哪些检查属于哪一类, 而结构性规则要尽量多。

确定性规则先跑,模型只看灰区

同一个模式的另一种表述:

先跑确定性规则:能判清楚的直接 block 或放行,模型只是一个可选后端。 常规命令在本地就决定了,根本不发给模型。

先做规则有一个容易被忽略的好处:它把攻击面缩小了。一个纯粹依赖模型的 判定器,面对提示注入时的行为是不可预测的;而一个先跑规则的判定器, 注入只能影响「规则判不了的那一小撮」。

有一份公开的注入测试报告值 300 次注入尝试,并且照实说明门挡住了什么、 什么又溜过去了。这种报告比一句「防注入」有用得多。

密钥与敏感数据:本地先拦

一个密钥检测器的设计把顺序讲得很清楚:

情况 处理
已知格式的密钥 本地拦截,根本不发给模型
未知的高熵字符串 先遮罩,再把遮罩后的文本发给模型
模型判定 ≥ 0.80 阻断
模型判定 ≥ 0.30 问人
模型不可用 也问人

两条设计决定值得记住:

  • 遮罩在前。 你不能为了判断「这是不是一个密钥」而把一个可能是密钥的东西 发到外部服务上去。判断本身也有泄露风险。
  • 模型挂了也问人,而不是放行。 这是 fail-closed 的一个具体形态 —— 服务不可用不是一个可以悄悄降低安全等级的理由。

实测:6/6 个密钥被拦住,0/6 个良性输入被误拦。

预算上限、签名回执与审计

有些检查与「模型说什么」完全无关,但必须在同一个系统里:

  • 一个运行时把危险命令阻断、预算上限交给确定性代码, 并用 Ed25519 签名回执链记录每一次裁决。
  • 另一个把风险分限制成代码控制的 0–100 分,并对每次裁决做 ES256 签名背书 (540 次调用,阈值 65–75 时 99.76%、0 误报)。

签名的意义不是防模型,是防事后说不清。当一个自动裁决导致了事故, 「当时模型说了什么、代码怎么处理的」必须能被独立验证,而不是只能相信日志。

注入要在两个位置筛

一个防护层的做法值得单独列出来,因为它指出了两个容易漏掉的位置:

在工具调用运行之前筛一次,在工具结果被 agent 读到之前再筛一次。

第二次是关键。只筛输入的话,攻击可以藏在工具返回的内容里 —— 一个被 agent 抓取的网页、一段读进来的文件内容,都会进入上下文, 而此时它已经绕过了输入侧的检查。

同类的另一条规则来自一个检索增强的实现: 引用来源里没有的数字,一律不输出。

沙箱与权限

除了筛内容,另一层是限制影响面:受保护的路径永远走人工确认; agent 的执行放在隔离的工作区里(有实现用 Docker); 子 agent 的权限与父级分开。

一张对照表

检查 属于哪一类 出错时倒向哪边
已知密钥格式 确定性规则 拦
高风险操作授权 结构性规则 拒绝(人批准才行)
只读命令自动放行 阈值规则 退回人工提示
工具输出里的注入 确定性过滤 + 模型 拦
「agent 做完没有」 效率门 放行(fail-open)
格式与风格检查 效率门 放行

表的第二列是关键:越靠近第一行的,越不该让模型经手。

一句话

能被概率覆盖的检查,最终都会被某个概率覆盖。 先决定哪些检查不允许被覆盖,剩下的才轮到调阈值。