跳到主要内容

Agent 的策略即代码 (Policy-as-Code):OPA、Rego 以及你的工具循环中缺少的决策点

· 阅读需 14 分钟
Tian Pan
Software Engineer

当监管机构第一次要求你证明你的支持代理在 3 月 14 日没有访问某位二级客户的账单记录时,你会发现关于你的鉴权架构的一个令人不悦的事实:系统提示词说“不要访问二级客户的账单”,YAML 工具清单说 tools: [search_orders, refund_order, get_billing],而在两者之间,模型做出了决定。由于不存在决策点,因此没有决策记录。代理是否做了正确的事是无法审计的,只能从发生的日志中推断。

这是智能体工程中没人画在架构图上的部分。如今的工具权限仍然存在于由创建智能体的人编辑的 YAML 文件中,通过描述意图的系统提示词呈现给模型,并由包裹每个工具调用的应用代码强制执行(如果真的执行了的话),例如 if user.tier == "premium" 检查。随着工具目录超过 50 个条目,且条件在租户、数据类别和用户角色之间成倍增加,这种手动构建的网格便不再具备扩展性,而系统提示词也不再是一个可靠的执行面。模型不是你的鉴权层,即使它的表现看起来像是一个鉴权层。

取而代之的是策略即代码(policy-as-code):一个专门的策略引擎 —— OPA 配合 Rego、AWS Cedar 或类似的声明式工具 —— 作为策略决策点(Policy Decision Point)位于每个工具调用之前。引擎在每次调用时只回答一个问题:给定这个主体(principal)、这个工具、这些参数和这个上下文,该操作是否被允许?智能体运行时(agent runtime)从未参与投票。这篇文章将探讨这种架构在实践中的样子,以及它所解决的四个即使是提示词工程也无法解决的问题。

提示词从未是鉴权层

将系统提示词视为策略表达极具诱惑力,因为它感觉是声明式的 —— 你写下规则,模型遵循规则,故事结束。失败模式在于提示词是被“解释”的,而不是被“强制执行”的。一个足够棘手的输入、一个微妙地暗示能力的工具描述,或者一个与规则冲突的目标,模型做出的“判断”就会产生策略违规,而这在对话记录中看起来与合法操作完全一样。2026 年的 OWASP 智能体应用前十榜单(OWASP Top 10 for Agentic Applications)将此称为“最小代理权(Least Agency)”失效:问题不再仅仅是智能体可以访问什么,而是它在没有检查点的情况下拥有多少自由度来对该访问权限采取行动。

YAML 清单也好不到哪去。在需要条件判断之前,它们还能扩展。一旦“这个智能体可以调用 refund_order”变成了“如果订单属于该主体的租户、订单时间小于 30 天、金额低于 500 美元且主体拥有 refunds:write 权限,则该智能体可以调用 refund_order”,YAML 的表达能力就不再足够,你开始在工具包装器中散布各种检查。六个月后,一项新的合规要求降临,而你无法找到所有需要更改规则的地方。

策略决策点(PDP)模式在 XACML 和云 IAM 中广为人知,它将这种逻辑外部化。应用代码变成了策略执行点(Policy Enforcement Point,PEP):它获取输入,询问 PDP,并根据答案采取行动。策略本身存储在单独的文件中,受版本控制,由安全团队审查,并可作为单元测试运行。智能体运行时像调用其他任何服务一样调用 PEP。模型在循环中没有发言权,因为根本没有询问模型。

OPA,Rego 以及 Cedar:引擎的选择

三种策略引擎出现在了智能体运行时的讨论中,它们之间的差异正变得越来越重要。

Open Policy Agent (OPA) 是 CNCF 毕业的通用引擎,使用 Rego 作为其策略语言。Rego 是声明式的,支持丰富的数据连接(你可以基于整个请求上下文以及通过 OPAL 加载的外部数据进行推理),并拥有成熟的生态系统 —— Sidecar、Wasm 包、语言 SDK。代价是 Rego 的学习曲线:它更接近 Datalog 而非 YAML,用英文表达起来很简单的策略可能需要一整个 Session 才能表达清楚。对于智能体运行时,前置于工具网关的 OPA-as-sidecar 是最常被提及的模式:网关在调用工具前调用 OPA,而 OPA 在不到一毫秒的时间内给出答复。

AWS Cedar 是较新的参与者,是一种专门为鉴权策略设计的开源语言,具有刻意受限的语法。Cedar 的设计选择 —— 默认拒绝、禁止胜于允许、与顺序无关的评估、无副作用 —— 使策略在组合时更容易推理。AWS 在 2026 年 3 月将 Cedar 作为 Amazon Bedrock AgentCore Policy 内置的策略引擎发布,它在网关边界拦截每一个智能体工具调用,并针对一组策略集进行授权。这些策略可以直接用 Cedar 编写,也可以从自然语言陈述中生成并形式化为 Cedar。自然语言路径很有趣,因为它承认编写策略才是最难的部分,而不是运行策略。

Cerbos 及类似的 PDP 服务 是为不想学习新语言的团队提供的 YAML 策略替代方案。策略定义在 YAML 中,评估时间在亚毫秒级,集成方式是 HTTP。权衡之处在于表达能力:需要连接外部数据的复杂策略在 Rego 中比在 Cerbos 模型中更容易实现。

对于大多数智能体运行时来说,引擎本身并不如架构上的承诺重要:每一次工具调用在执行前都必须获得 PDP 的决策。像对待数据库一样对待 PDP —— 选择一个,将其作为基础设施运行,赋予其明确的契约,停止在 TypeScript 中手动编写等效代码。

能力令牌取代 YAML 授权

即使部署了 PDP, “该智能体当前拥有什么权限” 这个问题仍需要一种表达方式。目前已趋于稳定的模式——且 IETF 关于智能体交易令牌(Transaction Tokens for Agents)的草案正在将其正式化——是使用短效的能力令牌(capability tokens)。这些令牌是通过 OAuth 2.0 令牌交换 (RFC 8693),从较长效的用户会话中根据具体任务派生出来的。

其结构如下:用户登录并获得一个生存时间(TTL)为数小时的会话令牌。当该用户启动一个智能体任务时,智能体运行时会将该会话令牌交换为一个能力令牌,其作用域被严格限制在该任务中——特定的工具、特定的资源范围、特定的租户——且 TTL 以分钟计,并明确规定不可刷新。如果智能体在任务中途需要新的能力,它会请求授权服务器,而不是去查 YAML 文件。

两个结构性细节保证了这一机制的运作。

交易令牌(Transaction Token)草案将 “委托人(principal)”(发起任务的人或上游系统)与 “执行者(actor)”(当前执行动作的智能体)区分开来。每一个策略决策都可以针对两者同时做出:“该 智能体 可以调用此工具,但前提是 委托人 拥有对该数据的底层访问权。” 混淆代理(Confused-deputy)攻击——即智能体被提示词注入,从而代表第三方使用其自身权限——变得难以实现,因为策略可以要求委托人与执行者达成一致。

第二个细节是能力令牌的作用域只能缩小,不能扩大。当智能体 A 产生智能体 B 时,A 传递给 B 的令牌是由授权服务器从 A 的令牌中派生出来的,其范围等于或窄于 A。父级智能体不能授予子级智能体它们自己不具备的能力;授权服务器在交换时强制执行这一点,而不是在应用逻辑中执行,这是随着代码库演进仍能保持正确性的唯一方法。

结果是,“这个智能体现在能做什么”不再是一个通过阅读 YAML 文件来回答的问题。它由当前的能力令牌决定,且该令牌的过期速度通常比大多数智能体引发的事故发生的速度还要快。

“模型拒绝”与“策略拒绝”是两回事

在典型的智能体对话记录中,有两种结果是难以区分的:模型拒绝是因为其训练数据告诉它要拒绝,而策略拒绝是因为规则禁止。在对话中,两者都表现为“我无法提供帮助”,从用户侧看都像是合规的表现。但在安全侧,它们的意义截然相反。

模型拒绝(Model refusal)告诉你模型的训练后安全机制认为该请求存在风险。对于合规性目的而言,它并不具备权威性——不同的提示词、不同的模型版本或越狱(jailbreak)手段都可能产生不同的答案。策略拒绝(Policy refusal)则是权威的:策略引擎评估了请求并予以拒绝,这正是监管机构希望看到的证据。如果你无法在日志中区分这两者,你就无法回答“我们的治理策略是否奏效”这类问题,只能给出一个软弱无力的回答:“模型表现通常正常。”

解决办法是对两者进行分别且结构化的记录。每次 PDP 调用都会生成一条决策日志:主体是谁、什么工具、什么参数(经过适当脱敏)、策略版本是什么、决策结果是什么、原因是什么。这些日志流向你的 SIEM。模型拒绝也会被记录,但属于不同的事件类别。特定工具上模型拒绝的激增是值得调查的提示词探测(prompt-probing)信号;而策略拒绝的激增则代表智能体配置错误或真实的策略违规尝试,具体取决于委托人的行为模式。

这也是结构化日志发挥价值的地方。审计记录需要是可解析的:智能体身份、委托人身份、工具名称、数据类别、操作类型、策略决策、策略版本、时间戳——所有这些都应作为离散字段,而不是埋在模型输出的自由文本中。SIEM 的异常检测针对这些字段运行。满足监管机构取证要求的记录,与在智能体开始探测其允许范围之外的权限时向安全团队发出警报的记录,是同一份记录。

工具循环中的身份传播

委托人传播(Principal-propagation)问题是导致原生实现最快崩溃的部分。代表用户 U 运行的智能体 A 调用工具 T1(该工具本身由下游服务实现)。T1 的授权层需要知道:是谁在请求?是智能体的服务身份?是用户 U?还是两者兼有?

错误答案——也是大多数早期智能体架构所采用的——是让智能体作为服务账号(service account)运行。每一次工具调用都以智能体身份进行授权,智能体拥有所有用户可能需要的权限并集,审计追踪记录为“智能体执行了该操作”,而没有链接回人类委托人。这就是服务账号的“自残陷阱(footgun)”:它在发生第一次跨租户数据泄露前都能正常工作,但到那时,由于缺乏委托人绑定,一个简单的 bug 就会演变成一场全平台事故。

正确答案是双重身份传播(dual-identity propagation)。能力令牌同时携带“委托人(principal)”(用户 U)和“执行者(actor)”(智能体 A)。每一个下游工具调用都会传播这两者。工具边界处的 PDP 会针对这两者做出决策:必须允许智能体调用该工具,且必须允许委托人访问该调用所触及的资源。如果智能体产生了一个子智能体 B,该链条将继续延伸——发送给 B 的令牌将 U 记录为委托人,将 A 记录为委派任务的直接执行者,并将 B 记录为下一跳的新执行者。SentinelAgent 和 OAuth 交易令牌草案都趋向于这种形式,因为没有其他形式能够支持事后回答“究竟是谁的授权链导致了这一动作?”。

对于实际工程而言,这意味着三件事。智能体运行时需要铸造并转发令牌,而不是 API 密钥。工具服务器需要读取这两个字段并将其传递给 PDP。策略制定者需要编写同时引用两者的策略——例如 principal.tenant == resource.tenant && actor in agent_class.support,而不仅仅是 agent_class.support。这些在概念上都不难,但在架构上具有足够的侵入性,以至于要在已上线的智能体架构上进行改造需要一个季度的工作量。

架构图中的呈现方式

最终确定的架构开始呈现出这样的形态。一个由用户发起的请求携带会话令牌 (session token) 进入智能体运行时 (agent runtime)。运行时在授权服务器上将该令牌交换为任务范围的权能令牌 (capability token)。智能体循环运行;在每次工具调用之前,运行时都会带着主体 (principal)、执行者 (actor)、工具和参数调用策略决策点 (PDP,如 OPA、Cedar 或同类工具)。PDP 返回允许/拒绝以及原因;允许则继续执行,拒绝则直接中断,并在日志中记录结构化的策略拒绝事件。工具调用将权能令牌携带至下游服务,该服务拥有自己的策略执行点 (PEP) 并调用其自身的 PDP —— 这是深度防御,因为即使智能体运行时遭到破坏,也不应能够伪造其自身的授权。来自每个 PDP 的决策日志都会流向 SIEM (安全信息和事件管理)。权能令牌在任务结束时失效。

对于服务间授权而言,这个图示中的内容并不新颖。新颖之处在于以同样的方式对待智能体:将其视为一个不受信任的客户端,其行为由策略引擎授权,而非由系统提示词 (system prompt) 约束其意图。

你终将面对的审计对话

采用这种架构的核心驱动力往往不是工程团队,而是审计对话。监管机构、企业客户的采购安全审查人员或内部合规负责人会问:“你能证明你的 AI 智能体在 3 月 14 日处理某个 Tier-2 客户的请求时,没有访问该客户租户之外的数据吗?”如果你的回答涉及查阅模型日志、总结提示词并指着 YAML 清单,那么这根本不是一个回答。能够过关的回答是:“这是该会话中每次工具调用的策略决策日志,经过签名且防篡改,显示了评估的策略版本、输入以及拒绝或允许的决策。”这一回答要求链路中存在 PDP,并且有结构化的决策日志流向你的审计流水线。

早早做对的团队会付出真实的工程成本:运营策略引擎、维护令牌交换流程、对接日志流水线,以及跨越“策略即 YAML”与“策略即 Rego 或 Cedar”之间的鸿沟。而晚些时候才补救的团队则会在收到监管信函后,将其作为一个紧急项目来偿还成本,面临着紧迫的最后期限,并可能选错供应商和设计方案。正确架构的形态已毋庸置疑;唯一的问题是每个智能体技术栈何时能跟上它。

模型不是你的授权层。你的授权层本就可以由授权层来承担。把它接入。

References:Let's stay in touch and Follow me for more thoughts and updates