文章指出,执行网关虽然用参数哈希防住审批后篡改,审核者看到的却可能是截断、折叠或重命名后的参数投影。若审批记录绑定原始对象而决策依据来自不完整视图,攻击者仍可利用两者差异绕过真实意图审查。
一种常见的审批网关,会在请求签署批准之前先冻结一份证据包。这个证据包包含工具名称、基于参数规范化序列化结果计算出的 args_hash、策略版本,以及足以将后续执行与先前审批关联起来的请求元数据。审批通过后,网关会再次序列化参数、再次计算哈希;如果哈希发生变化,就拒绝执行。这是良好的工程实践。它堵住了一个真实存在的检查时与使用时不一致(TOCTOU)漏洞。
但还有一个更隐蔽的缺口:审核者从未见过那份规范化序列化结果。
审核者看到的是一张审批卡片。卡片只呈现参数中经过挑选的一部分,而且通常采用为提高审核速度而优化的布局:操作名称、目标显示名称、金额、风险标签,也许还有一段由上游生成的简短说明。长字符串可能被截断,嵌套对象可能被折叠,值为 null 的字段可能被删除,内部标识符可能通过读模型转换成易于理解的名称。审核者是依据这个投影视图做出决定的。
持久化记录绑定的是规范化对象,而决定依据的是渲染后的视图。两者并不是同一份工件。
如果渲染器遗漏了 destination_account,两个请求就可能产生不同的 args_hash,却显示完全相同的审批卡片。一个请求把资金发送到 acct_7K4...,另一个则发送到 acct_9PQ...。两者的规范化字节不同,所以哈希确实发挥了作用;执行前的检查也确实发挥了作用,因为它证明了获得批准的字节就是最终进入执行环节的字节。然而争议依然无法解决:审核者以为自己批准的是哪个收款账户?记录无法回答,因为它从未绑定真正促成决策的那份内容。
这种不对称让问题比普通的界面缺陷更加严重,因为审批卡片越简略,审计记录反而显得越干净。一张整洁的卡片配上匹配的哈希,事后看起来就像一位合格的审核者认真检查过这项操作。这份工件为 payload 赋予了密码学意义,也为视图赋予了社会意义,但其中只有一个被真正记录了下来。
早在 Agent 系统采用持久化审批之前,硬件钱包就已经为这类故障命了名。设备签署的是一个对象,而签署者看到的是它的投影。这个投影可能不完整、存在歧义,或者由一条可信度较低的路径提供。签名依然有效,但不能仅仅因为相关字节受到了保护,就认为同意声明具有同等强度的可信度。
Agent 基础设施正在用更完善的日志系统重新制造同一个缺口。
投影代码通常与执行约束的代码相距甚远。策略引擎读取权威状态,而审批卡片读取的是为降低延迟而构建的反规范化模型。审计服务存储事件 ID,而审核者看到的则是名称、标签、摘要和缩短后的值。
这种分离是正常的架构设计。但它也意味着,审批决定依赖的输入不只有规范化 payload。
以一个事件溯源的权限系统为例。授权和撤销事件会进入只追加日志。投影服务消费这份日志,并维护一张表,用于回答某个 actor 是否可以对某项资源运行某个工具。执行约束会查询这张表,因为针对每个请求读取并归并完整事件流的成本太高。
一条撤销事件已经写入日志,但权限投影出现了延迟。在这段延迟期间,权限检查仍然返回 allowed,操作继续执行,随后生成的证据列出了用于证明这次决定合理的授权事件 ID。这些 ID 都是真实的。它们经过签名、存在于投影之中,而且相对于检查器当时读取的状态,这份记录也是完整的。但记录没有说明的是:检查器读取了一个过期的投影。
后续调查可以在日志里看到撤销事件,也可以在证据中看到授权事件。如果没有绑定决策时使用的投影状态,调查就会演变成一场关于时序和代码路径的争论。检查器是在撤销事件变得可见之前完成读取的吗?还是证据构建器和权限网关查询了不同的数据存储?两种解释都说得通,但审批工件中没有包含任何一种解释。
测试之所以无法发现这个问题,是因为测试抹平了产生问题的架构。一项测试先追加撤销事件,然后立即在同一进程中调用 can_execute(),而内存中的投影会同步更新。所有测试顺利通过。生产环境中的故障存在于两个数据存储和一个消费循环之间,但测试把这些组件压缩进了同一个调用栈。任何无法表达投影延迟的审批测试,也无法验证对投影敏感的决策,因为实验中根本不存在那个真正关键的变量。
渲染测试也存在同样的不足。单元测试会检查 destination_account 发生变化时 args_hash 是否随之变化,快照测试会检查卡片看起来是否合理,但二者都不会追问:每一项与安全有关的差异,是否都保留在了最终显示的投影中。完美的字节完整性完全可以和糟糕的决策完整性同时存在。
当做出决定的一方是模型时,同样的缺口依然存在。模型看不到数据库行、工具结果或服务器目录本身。它看到的是序列化后的上下文,而这些上下文由 prompt 模板、截断规则、排序系统、摘要器、适配器,有时还有其他 Agent 共同生成。这些组件都是渲染器。
一项工具结果可能包含十一个字段,模型只能看到其中四个。一台服务器可能暴露一百个工具,模型只能看到为当前请求检索出的十个。先前的子任务可能生成了一份结构化 trace,而 supervisor 看到的只是一句声称任务成功的描述。模型依据这个视图做出决定,审计记录绑定的却是上游对象或下游操作。
Guard 谓词能够清楚地体现这种风险。许多 Guard 都以否定形式编写:输出不得包含原始凭据;摘要不得声称某项失败的操作已经成功;下一个请求不得包含被禁止的 scope。这些谓词针对渲染后的文本或结构化摘录进行求值。空白的渲染结果可以满足所有否定谓词。一个静默失败且什么也没有返回的 subagent,可以通过一整面这样的关卡,并且每一步都是绿色,因为网关衡量的是行为的投影,而退化为空的投影恰好看起来最安全。
肯定式谓词采用不同的形式。它可以要求渲染结果必须包含 destination_account、amount、tenant_id 和 tool_call_id,然后才能继续做出决定。这仍然不能证明这些值是正确的,但它可以把静默遗漏转化为显而易见的缺失。在高度依赖投影的系统中,只使用否定谓词并不是一种可靠的手段,因为“一无所有”看起来太像“完全合规”。
评估框架也继承了这种盲点。一项检索评估可能会把查询交给工具检索器,然后根据正确工具是否出现在返回列表中进行评分。它衡量的是检索已经被调用之后的排序质量,却没有采样更早的那个分支:模型是否调用了检索器。
于是,评分提高了,故障却纹丝不动:检索器被调用时能够返回更好的候选项,但 Agent 恰恰会在最需要检索的请求中跳过检索。测量从它声称要覆盖的决策下游才开始,因此按照设计,那些真正值得发现的故障从一开始就不在抽样框之内。
工具使用审计也在重复同样的错误。日志记录了最终调用及其规范化参数,而生成这次调用的 prompt 片段,则在事后通过模板和源对象重新构建。如果某条截断规则删除了唯一的警告,或者摘要器把一项硬性约束替换成了一句含糊的描述,审计就只能假定渲染路径是忠实的。这个假设在运维层面或许合理,但不应该被包装成证据。
每一条面向模型的路径中都存在两个对象:记录中的对象,以及上下文中的对象。安全审查通常绑定前者,而模型依据后者采取行动。
建设性的解决办法并不复杂:把投影也绑定起来。
在 args_hash 旁边存储一个 rendered_view_hash。它应当由一个固定版本、带版本标识且具有确定性的渲染器,基于呈现给决策方的确切材料计算得出。对于面向审核者的审批流程,这些材料可能是卡片视图的规范化 JSON,也可能是发送给客户端的确切文本和字段集合,具体取决于信任边界位于何处。对于面向模型的流程,它就是 Guard 或决策实际消费的序列化上下文片段,其中包括实际提供的截断内容、检索结果和摘要。
这样一来,记录便能提出两个彼此独立的声明。args_hash 表明哪个 payload 被送去执行;rendered_view_hash 表明决策依据的是哪个视图。发生争议时,我们将拥有两份工件,而不是一份工件,再加上一个关于渲染器的假设。
这并不意味着必须永久保存每一份渲染视图。小型审批卡片的存储成本足够低,可以直接完整保存。对于更大的上下文窗口,只保存哈希通常就够了,前提是能够通过原始对象、渲染器版本和截断参数重现该视图。如果视图无法重现,哈希仍然可以判断后续重建的内容是否与当时呈现的内容一致。
渲染器由此进入可信计算基(TCB),而这项成本是真实存在的。一个此前被视为产品表层或 prompt 管道的层,现在变成了证据路径的一部分。它需要具备确定性的行为和版本标识,还需要通过测试来断言:在符合现实情况的截断、本地化、feature flag 和空状态渲染下,与安全有关的字段依然能够保留下来。
待处理的审批也会与渲染器版本产生耦合。改进审批卡片可能会导致仍在等待处理的审批失效,因为新卡片计算出的哈希不再与旧视图相同。继续保留旧版渲染器可以避免这种失效,但代价是承担一项会随保留周期不断增长的兼容性负担。无论选择哪种方案,这都是一项架构决策,而不是一个样式细节。
绑定投影还能明确遗漏的责任归属,但这比彻底消除遗漏要弱一些。如果审批视图遗漏了 destination_account,审核者依然缺少这个关键信息。真正发生变化的是证据层面:遗漏本身会成为记录的一部分,后续分析可以准确说明当时展示了什么、由哪个渲染器生成,以及最终执行的是哪个规范化 payload。
这种清晰度会改变激励机制。为了可读性而隐藏某个字段的提议,不再只是对界面的修改,而是对决策工件的修改。摘要器也不再是不可见的管道,因为它正在生成授权决策的一项输入。
这条通用原则可以迁移到各种场景。面对任何形式为“参与方 X 批准、验证或证明了 Y”的工件,都应该追问 X 实际读取了什么,然后再追问:是否能在不假定渲染器忠实可靠的情况下,根据记录重建那个对象?如果重建依赖这种假设,那么这份记录只不过是一个关于渲染器的断言,却披上了签名的外衣。
密码学可以证明特定字节确实经过了执行路径。但除非视图也受到绑定,否则它无法证明审批方看到了那些真正重要的差异。哈希覆盖的是 payload,视图也需要属于自己的声明。
对于后续操作,你可以考虑屏蔽此人和/或举报滥用行为。