关于"谁来治理 AI",2026 年 9 月的两周里出现了四种彼此不同的答案,而且提出者全部来自造系统的人自己。
Demis Hassabis 提议成立一个由行业出资的监督机构,大致参照 FINRA 的模式,用于在模型部署前做前沿模型测试。Dario Amodei 呼吁各实验室放慢前沿模型研发,并给独立评估方真正的访问权限;几天后,Anthropic 与 Accenture 达成合作,承诺在五年内投入至少 20 亿美元用于嵌入式评估。OpenAI 则开始对外发布报告,披露模型出现的意外行为。据《华尔街日报》报道,Mark Zuckerberg、Elon Musk 和 Jensen Huang 分别与特朗普总统沟通,说服他放弃设立该监督机构,理由是一个统一的监督&管理者反而会保护最大实验室的领先优势,企业靠自查就能保证自家系统安全。
这些立场你未必认同,但它们有一个共同的盲区:没有一条在讨论真正决定你的应用里的 agent 能不能发邮件、轮换密钥、合并分支或者划转资金的那段代码。 这个决定不是实验室做的,也不是写在某份政策文档里的。它由你自己代码里的一个函数做出,返回 true 或 false。
一个真实存在的 bug:规范里没说清楚的部分
这不是思想实验。在 PIC(一个用于执行前校验 agent 动作的开源协议,作者本人维护)的一次独立压力测试中就发现了一个缺陷:schema 接受大写的 SHA-256 摘要,参考运行时悄悄把它归一化后继续执行,而规范从头到尾没说这种表示形式是否合法。
没有密码学失败。协议只是存在多于一种合理解释,而这比"密码学直接坏了"更难被发现。作者怀疑,今天运行的很多 agent 部署都有同样的问题。
治理的终点是一个布尔值
Stack Overflow 2026 年 4 月对 1100 名开发者和技术从业者的脉搏调查发现,60% 的人会阻止 agent 做出未经批准的系统变更。这个做法合理,但它绕开了最难的部分:什么算"已批准",以及链路上的每个组件是否会对这个答案达成一致。
来看一个 agent 提出的付款请求。它给出自己打算调用的工具、影响这次决策的输入,以及支撑"付款已获授权"这一主张的证据:
{
"action": {
"tool": "payments.transfer",
"args": {
"amount": 500
}
},
"impact": "money",
"provenance": [
{
"id": "invoice",
"trust": "trusted"
},
{
"id": "invoice",
"trust": "untrusted"
}
],
"claims": [
{
"text": "The invoice was approved",
"evidence": [
"invoice"
]
}
]
}
JSON Schema 会确认 provenance 是一个数组、每个条目都有标识符和允许的信任取值、必需字段齐全。除非协议自己额外加规则,这份 payload 就是合法的。问题在于:**标识符不唯一**,而 claims 里的证据引用现在同时指向了两个不同条目。
假设策略很简单:只有当主张背后的证据来自可信来源时,付款才能执行。(暂时先按面值采信 payload 里的信任标签,后面再谈这些标签由谁来设定。)要执行这条策略,校验器必须先确定 `"invoice"` 到底指哪个条目——而两位合理的工程师会得出两种不同的实现。
def resolve(provenance, evidence_id):
for entry in provenance:
if entry["id"] == evidence_id:
return entry return None
const index = Object.fromEntries(provenance.map(e => [e.id, e]));
const resolve = (id) => index[id];
两段查找单独看都不是 bug,都是再正常不过的惯用写法,而规范从未说明哪个条目应当胜出。对上面那份 payload,实现 A 把证据解析到可信条目,付款照常执行;实现 B 解析到不可信条目,付款被拦下。两个校验器跑的是同一个协议版本、同一套策略、同一组可信密钥,唯一的差别只是各自查找 id 的方式。
于是同一串字节,用同一个 schema 校验,得到两个不同的结果;而攻击者只需要知道对面跑的是哪个服务。
schema 完成了它的工作,但这份工作是有边界的:它校验 payload 的**形状**——字段是否齐全、类型是否正确、取值是否在允许范围内。它也能表达一部分字段间的规则,比如"如果存在这个字段,那么那个字段必填"。但它无法表达这份 payload 真正需要的规则:claims 中作为证据引用的 `"invoice"`,必须且只能对应 provenance 中恰好一个条目。标准 JSON Schema 没有办法说清"文档某处的某个值必须唯一匹配另一处的某个值";uniqueItems 也帮不上忙,因为这两条的 trust 取值不同,在 schema 眼里它们就是两个不同的项。付款是否放行完全取决于这条链接,而 schema 把解释权留给了每个校验器。
这是解析器差分,比智能体早得多
安全领域的人对这类问题再熟悉不过:两个组件用两种不同的方式读同一份输入,两者之间的差异就是漏洞所在。HTTP 参数污染是一例——代理读第一个 id,应用读最后一个。XML 签名包装是另一例——校验器验证的是某个元素上的签名,解析器读的却是另一个。还有 JWT 算法混淆,token 本身就写明了该用哪种算法来校验它;以及 HTTP 请求走私,代理和它后面的服务器对"一个请求在哪里结束、下一个从哪里开始"意见相左。 这类问题存在了大约二十年,我们从中得到的教训相当朴素:当两个实现需要就"同样的字节意味着什么"达成一致时,规范中任何一处留白的细节都可能被读出两种意思,攻击者迟早会利用它。
影响与建议
- 治理讨论停留在"谁来监督&管理"层面,而真正的授权判定发生在每个团队自己的代码里;把注意力放在行业层面的机构设计上,不会让一个
resolve()函数自动变安全。 - 对 JSON Schema 这类形状校验要保持清醒:它能保证结构合法,保证不了语义唯一。凡是跨字段的引用关系(证据指向来源、附件指向正文、权限指向角色),都需要额外的一致性约束,否则就交给实现者各自"合理"发挥。
- 把协议规范里的每个留白当作潜在的攻击面来审:明确 id 唯一性、明确归一化规则(例如摘要是否允许大写)、明确重复项的处理顺序。
- 在落地层面,可以先把"同一份输入,两个实现给出不同结论"当成一类必须被测试覆盖的缺陷类型,与超时、重试、幂等放在同一优先级上。
[Source: TechRepublic / Dario Amodei / Anthropic / NPR / Forbes(引述《华尔街日报》)] Agent 协议正在遇到和 PIC 当初一样的问题,我之前提到的压力测试就发现了这些缺口。处理方式是收紧规范,并把每个案例补进下面会讲到的合规性测试里。这些协议大多才一两年历史,还没像 token 和签名格式那样被反复打磨过——所以我认为,今年写出来的治理层,应当用审视新 token 格式时同样的谨慎态度去检验。
有益的规范化,正在悄悄改写协议语义
开发者几乎本能地会归一化输入:裁掉空白、把标识符转小写、补齐 Base64 的 padding、接受等价编码。这些做法本身没错——拒绝明显可以还原的数据显得不近人情。问题在于,这种直觉放在搜索框里是合理的,放在授权边界上却是危险的。 假设策略只允许一个工具:
expected_tool = "payments.transfer"
proposed_tool = "payments.transfer "
严格按字符串比较,这个提案会失败;如果两边都先调用 `strip()`,它就通过了。两种行为单独看都不算不合理,真正的缺陷在于:具体实现可以自己挑一种。
涉及签名时,问题比表面上更严重。SHA-256 摘要通常渲染成十六进制,而大写 A–F 与小写 a–f 表示同一个数值,所以把输入转小写看起来无害。但它其实不是这样,因为签名覆盖的是字节而不是数字。如果生产者签署的载荷里是大写形式,而验证方在构造规范化载荷前先转小写,那么一个真实合法的提案,验证就会失败。
反过来说问题更糟:如果验证方先用原始字符串校验签名、再把归一化之后的值拿去套策略,那么它执行的就已经不是被签署的那份内容了。对十六进制摘要来说,转小写得到的数值相同,似乎没区别;但同样的习惯套到别的字段上,就可能改变命中哪一条策略规则——比如一个被裁掉尾部空格或转成小写的工具名,可能匹配上原始签名名称匹配不到的白名单条目。到了这一步,策略作用在一个签名者从未签过的值上。
Base64 有同样的毛病:有的库容忍内嵌空白,有的自动补 padding,有的悄悄把 URL-safe 字母表转换过来,另一些则三种情况一律拒绝。当一份签名决定某个动作能否执行时,你的 Base64 库做出的这些选择就成了授权策略的一部分——尽管团队里没有任何人写过或审过它们。
这里最好把两个决策分开看。
第一个是**被签名的载荷如何转换成字节**。JSON 的 RFC 8785、XML 的 c14n,以及 COSE 中的确定性编码规则,会固定字段顺序、数字格式和转义方式,让每个实现签下的字节完全一致;但它们不改动字符串内部的内容,所以 RFC 8785 会把大写摘要原样保留为大写。
第二个是**协议究竟接受哪些取值**。这里你可以选择归一化(例如签名前和校验前都把所有摘要转小写),也可以只接受唯一形式、其余全部拒绝——大写十六进制失败、缺 padding 的 Base64 失败、工具名带尾部空格也失败。
PIC 两条路都走了。在规范化证明(canonical attestation)模式下,它签署的是规范化后的序列化结果(同时仍保留一个校验原始载荷字节的遗留模式);而在取值这一层,它选了第二条路,原因很简单:归一化意味着每种使用该协议的语言都要重新写一遍这段代码,而 c14n 缺陷史已经说明,同一套规则的不同实现会像解析器一样产生分歧。有了严格拒绝,格式错误的载荷在抵达时就被带明确错误挡下,而不是先放行、等到之后两个验证方各执一词时再排查——那时问题难追得多。如果你在设计类似机制,请选择你能在规范里完整描述清楚的那条路;但一定要选,因为不选的话,一份签名提案的含义就取决于恰好是哪个库在解码它。
信任不能来自请求授权的一方
最要紧的歧义其实与编码无关,而是:谁有资格声明某个输入可信。
{ "id" : "invoice" , "source" : "email_attachment" , "trust" : "trusted" } 这个字段很容易序列化,但单看它几乎无法给验证方提供任何可依赖的信息。提案产生在验证方上游,而且常常就是那个正请求授权去行动的 agent 本身。若照字面接受这个字段,请求方就等于自带授权来源——这就是经典的 confused deputy 问题,只不过是用 JSON 写出来的。
信任必须在执行边界内部推导出来。生产者可以标明来源并附上证据,而验证方负责判断这些证据是否最终指向操作者真正信任的东西,比如一把已知的签名密钥。
effective_trust = "untrusted"
if signature_is_valid(evidence, operator_keyring):
effective_trust = "trusted"
if proposal.impact == "money" and effective_trust != "trusted":
return BLOCK
这里最关键的是**完整性**(数据没有被改动)与**授权**(确实有权批准的人批准了)之间的区别。哈希匹配只能证明验证方收到了它预期的字节,却完全没说明这些字节是谁产生的、谁批准的、是否应当被授权去发起支付。攻击者可以提交一个文件以及该文件正确的哈希:完整性检查通过,因为确实没被改动,但没有任何有权限的人批准过任何事。
只有在验证方于操作者信任的密钥列表中查到签名密钥时,签名才携带授权——而这份列表提案本身没有任何办法往里添加;签名还必须覆盖真正重要的字段:工具、参数、影响级别、相关来源信息以及有效期。否则一份真实签名可能批准的是一件事,落到执行的动作却是另一件事:比如一份针对 500 金额转账的签名,最后被挂在一个 50,000 的请求上。
换句话说,是否可信由验证方在核验证据之后给出,而绝不能由 agent 自己贴上这个标签。
在你已经在运行的技术栈中的落点
落到工程实践上,两条主线是分开的:一是把"被签名的字节如何生成"交给已有的标准(RFC 8785、XML c14n、COSE),保证所有实现签下的东西一致;二是把"协议接受哪些取值"在规范里写死为唯一形式,用清晰错误提前拒绝,而不是依赖各语言、各库各自的宽容。 同时,把信任判定留在验证方内部完成,密钥列表由操作者掌控,签名覆盖工具、参数、影响级别、来源与有效期这些关键字段。 只要看看一条工具调用在真正落地前要穿过多少环节,就知道"同一个字段到底谁说了算"为什么成了难题。
以 Model Context Protocol 为例:工具定义用 JSON Schema 描述输入参数,各语言有各自独立的 SDK,宿主程序在之上再加一层自己的审批提示和白名单,而在参数抵达工具之前,它通常已经被模型、客户端、传输层和宿主策略检查各读了一遍。 同一份数据被这么多段独立代码分别读取,每一段都可能读出略微不同的理解。 你自己写的审批环节也一样——那段在应用真正执行之前校验模型工具调用的代码。 好消息是,你不必采用任何人的协议才能验证这一点。

拿你自己那条审批路径,问三个问题:同一个字段如果出现两次,哪一个说了算,所有组件的判断是否一致? 工具名如果带了一个尾部空格会怎样,在 CLI 里和在 HTTP 桥接层里是不是同一回事? 载荷声称自己可信,那么这个判断是谁做出的?
三层,而不是一层:Schema 校验结构,不校验权威
JSON Schema 描述的是 JSON 数据的结构,它在必填属性、类型、枚举、正则模式和组合结构上确实好用。这些控制仍然重要——尽早拒绝格式错误的输入,语义层要操心的情形就少一些。 但真正决定"代理是否应当行动"的问题,通常涉及不止一个字段,有时还不止一个系统:一个证据标识是否恰好解析到唯一一条来源记录?签名覆盖的,是不是此刻正在被提出的那个工具和那组参数?验证时使用的签名密钥,是否在当时就是可信的,而不是仅仅在生成时可信?当多个检查同时失败,哪一个错误优先? 如果你把这些全部压进一份 schema,得到的只是一份看起来精确的文档,却依然没有说明运行时应该如何行为。更强的设计是把它们拆成三层,每一层都收窄下一层能看到的东西:
- Schema 校验:拒绝结构不合规,以及不符合格式约定的表示。
- 语义验证:解析引用、校验绑定关系,并推导出有效信任状态。
- 策略评估:判定这个已验证的提案是否获准在此时执行此动作。
这三层各自都需要独立的代码和测试。文档里写一句"请实现方自觉做出合理行为",替代不了其中任何一层。
一致性测试向量就是那份可执行的规范
书面规范永远覆盖不到作者没想象到的每一种解释。 第二套实现往往几天内就会暴露这些缺口——换成另一种语言、另一个库、另一个人,就会做出不同的、同样合理的选择。 务实的答案是:写出一组可以实际运行的测试向量。 每个向量包含确切的输入、期望的判定结果,以及在必须失败时给出一个稳定的错误码。 所有实现跑同一套向量,并且刻意覆盖那些普通应用测试根本走不到的边界情形。 向量的作用不止于捕捉回归。 它还界定了协议在"人们可能有分歧"的情形下如何表现;而一旦某个含糊场景被定下来,它就变成一个永久存在的测试用例,而不是又一段留给后来实现者自行解读的段落。
错误上报的顺序同样应该用这种方式确定。 同一份提案里可能同时存在重复标识、错误工具名和无效签名。 如果一个实现先报重复标识、另一个先报签名无效,运维人员对同一份输入就会得到不同的处理建议——差别只取决于当时调用了哪个服务;而每个适配层最终都会长出自己的顺序。 决定哪一项检查先跑,并把它写进测试向量,行为才变得可预测,调试也容易得多。
安全默认值必须扛住每一层适配
验证器本身可以很严格,却仍然被周围的代码架空。常见的几种情况:CLI 开启了严格检查而 HTTP 桥接层没有;框架适配器在主验证器更新几个月后仍沿用旧的宽松默认值;或者调用方为图省事关掉了证据检查,继续接受那些自称"可信"的输入,却没意识到两者叠加起来并不安全。 因此,每一条通往验证器的入口(CLI、HTTP 桥接层、每个框架适配器)都必须以同一套严格设置启动。当协议演进时,你可能仍需在一段时间内接受旧字段或更宽松的旧行为,好让基于上一版本构建的用户有时间迁移——这本身是合理的,但旧行为只应当在有人显式开启时才生效,而且一旦启用,软件应当给出清晰的警告。如果一个迁移开关能在不通知任何人的情况下把旧行为请回来,那么某个部署是否安全就取决于它碰巧是怎么配置的,而这极少被写进文档。
为什么关于治理的争论让问题更糟,而不是更好
今年早些时候我曾论证过:代理会暴露出组织从未真正编写过的安全检查,因为这些检查一直寄居在人的直觉判断里。 现在看来,把它们写下来只是第一步——取代那份直觉判断的代码,还必须在它运行的每个地方表达同样的含义。 也正是在这个意义上,关于治理的那几种立场中,有一种比其他几种更关键。" 每家公司各自负责自己 AI 的安全、不存在一个共同机构来定义什么叫安全"——这种说法描述的是许多独立团队各自做出许多各自合理的决定:关于归一化、关于信任推导、关于错误优先级。
公司之间的分歧与解析器之间的分歧并不等同;但在实际效果上,它会导致同样的局面——不同实现对同一个问题给出不同答案。 这种担忧本身是合理的;一个由最大几家实验室组成的测试机构,很可能确实能让这些实验室继续领先。 但如果顶层没有就共同规则达成一致,这些规则就只能在更底层达成一致——由写验证器的人,在公开场合、通过每个实现都能运行的测试向量来达成。 而实际工作中,这里面相当大一部分会落到开发者头上。
治理最终落在验证器上。
AI 智能体的安全控制正在成为一场关于「谁有权放行」的争论,但真正落地到工程层面的问题往往要朴素得多:决定工具是否执行的那些判断,能不能在不同的系统之间保持一致。 策略(policy)、结构化模式(schema)、签名、人类审批,这些机制都有价值,但它们本身不会拦住一个智能体。真正起作用的是那段把它们翻译成「是」或「否」的代码——而且这段代码必须在工具执行之前跑完。 这段代码不需要预判先进模型可能做出的所有行为,它需要承担的职责要短得多,也具体得多:
- 拒绝语义含糊、无法唯一确定的标识(identifier)
- 按既定规则对安全敏感的值做精确比对,而不是模糊匹配或近似判断
- 把「完整性」(integrity)与「授权性」(authority)当作两件不同的事来区分
- 只依据校验器自身能够独立核实的证据来判定信任,而不是依赖外部声明或模型自述
- 对于同一个提案与同一份策略,在协议实现的所有位置都返回同一个结论
核心变化:分歧才是风险所在
问题出在实现层:如果上面这些职责被下放给各个实现自行处理,那么在完全相同的策略下,一个服务可能会放行一个提案,而另一个服务却将其拦截。 公共讨论的焦点通常落在 AI 应当推进多快、谁来当裁判这类宏观问题上。但对写代码的人来说,问题可以简化为一个更朴素的判断:今天控制这些智能体的系统,能否对它们正在读取的内容达成一致? 这不需要靠信任来回答,可以通过实测得出:把同样的测试输入分别送进每一个系统,然后比对它们的输出。
影响与建议
- 把上述判定职责收敛到协议或共享校验层,而不是散落在每个接入方自己的代码里,否则策略一致性无从保证。
- 在上线前跑一轮「同一输入、多个实现、比对结果」的交叉测试,把分歧显性化,而不是等线上出现不一致再回溯。
- 把完整性校验与权限校验拆成两条独立链路,避免用一个模糊的「可信」状态同时承担两种语义。
- 对信任判定,只接受校验器可以自行核实的证据,把模型或外部服务的自述排除在外。
- 关注结论的可重复性:同一提案、同一策略,在不同部署位置上应得到同一裁决结果。
这类控制逻辑的价值,最终体现在它是否能让不同系统在同一条规则下做出完全相同的判断上。


评论