跳到主要内容

Agent IAM 不等于 Service IAM:为什么当意图在运行时构建时 OAuth 会失效

· 阅读需 13 分钟
Tian Pan
Software Engineer

Bearer Token 模型有一个智能体正在悄然违反的假设:调用者在发起请求时知道自己想要什么。OAuth 作用域、IAM 角色和 API 密钥都是围绕一个在身份验证开始前意图就已经确定的主体设计的。你的 CI 运行器意图稳定。你的微服务意图稳定。智能体则不然。智能体的意图是在请求时,由用户提示词、系统提示词、检索到的文档以及可能由攻击者编写的工具输出共同组装而成的。当智能体去获取令牌时,IAM 层必须做的策略决策实际上已经做出了——而决策依据的输入,IAM 层从未见过。

这就是为什么在服务间通信中行之有效的身份验证模式,现在正引发一类没人能准确描述的事故。提示词注入窃取了长效的 Bearer Token。智能体在不同会话间“记住”了权限,因为令牌的寿命超过了用户的意图。一个理应需要三个作用域的多步任务,在整个会话期间都持有所有权限,而不是按步骤获取和释放。严格来说,这些都不是 OAuth 的 bug。它们是试图将假设静态意图的模型扩展到覆盖一个每轮对话都在重构意图的调用者所导致的后果。

本文的论点很明确:智能体 IAM 与服务 IAM 是不同的问题,将它们混为一谈正是导致事故的原因。修复的方法不是“更谨慎地使用 OAuth”。而是绑定意图的作用域——这种作用域在任务结束时失效,而不是在令牌 TTL 到期时失效;是记录哪个提示词请求了哪个工具调用的委托链;是标明其所对应的用户可见任务的能力令牌;以及一个能够区分用户输入的指令与通过检索文档引入的指令的策略层。OAuth 模型是一个合理的底层架构,但它不是完整的答案。

其它一切所继承的静态意图假设

让我们梳理一下 OAuth 访问令牌(Access Token)实际代表什么。用户点击授权屏幕,授权客户端代表他们处理一系列固定的作用域,然后授权服务器签发一个带有 TTL 的令牌。接着,客户端向资源服务器出示该令牌。资源服务器检查签名、受众、过期时间和作用域。如果这四项检查都通过,调用就会成功。隐含的契约是,获得令牌的人同意将其用于用户授权的目的,且资源服务器相信作用域是该目的的合理代理。

对于传统客户端,这种方式行得通,因为客户端的意图是静态的。一个拥有 drive.readonly 权限的备份工具会用它来读取云端硬盘文件。一个拥有 payments:create 权限的发票服务会用它来创建支付。从“令牌上的作用域”到“客户端将要做的事”的映射是一对一的,且对于配置 OAuth 流程的开发者来说是预先已知的。

对于智能体,这种映射是一对多的,并且是在运行时决定的。一个拥有 drive.readonly 的智能体可能会使用该作用域来总结用户粘贴的文档,也可能枚举每个共享云端硬盘以查找字符串,或者——在提示词注入下——将用户从未询问的文件内容泄露给受攻击者控制的工具调用端点。作用域是必要的,但不再充分。令牌只说了“这个主体可以读取云端硬盘内容”,它没有说“仅针对此任务,代表此用户,仅针对用户命名的文件,且仅在此轮对话结束前有效”。所有这些约束都存在于用户的脑海中,但没有一个存在于令牌里。

第一个教训是作用域太粗糙,无法表达智能体的意图。第二个教训是令牌 TTL 绑定的时间维度不对。一个 60 分钟的访问令牌比大多数智能体任务的寿命长了一个数量级,而从“任务完成”到“令牌依然有效”之间的窗口,正是被劫持的智能体利用用户权限执行非预期工作的危险期。

出错的三个常见失效模式

第一种失效模式是作用域塌陷劫持 (scope-collapse hijack)。团队给智能体一个具有广泛作用域的令牌——如 drive.readgmail.sendcalendar.write——因为这是智能体规划器(planner)拥有足够空间来选择工具的底线。然后,一个提示词注入(通常通过检索到的文档或工具结果)到来,并指示智能体执行一个以用户从未要求的方式使用所有三个作用域的任务。从资源服务器的角度看,每一次调用都是经过充分授权的。从用户的角度看,智能体做错了事。没有任何 IAM 信号可以区分这两种情况,因为 IAM 层不知道哪个提示词产生了哪个工具调用。审计日志完美地回答了“谁调用了 API”,却完全无法回答“智能体为什么要这样做”。

第二种失效模式是持久化权限 (persistent permission)。智能体获取令牌,完成任务,然后令牌在会话剩余时间内一直留在智能体的会话内存中。随后的某轮对话——可能是一个不同的任务,也可能是一个偏离轨道的后续跟进——使用了同一个令牌。用户从未重新授权,令牌的 TTL 也未过期。IAM 层没有“任务已结束”的概念,因此权限的寿命超过了证明其合理性的意图。在服务 IAM 中的修复方法是缩短 TTL,但缩短 TTL 只是转移了问题;正确的边界不是墙上时钟的时间间隔,而是用户命名的任务生命周期。

第三种失效模式是针对多步任务的会话级令牌 (session-scope token over a per-step task)。智能体的规划器勾勒出一个多步计划,该计划合理地需要三个作用域——例如读取文件、转换文件、发布结果。智能体并没有在需要每个作用域的时刻获取并在之后释放,而是在整个会话期间持有所有这三个作用域。第 2 步的权限被攻陷意味着第 3 步的作用域也会被滥用。最小特权原则具有时间轴维度,这在服务 IAM 中很少被行使,但智能体却使其成为了核心环节。

所有这三种失效模式都有一个共同的结构。IAM 层做出了一个单一的决策——“这个令牌对这个作用域有效吗?”——而智能体的行为将这个单一决策变成了一个开放式的行动许可。原本假设意图静态的模型,正被要求去管理一个意图在调用边界处不断被组装、博弈和重写的调用者。

意图绑定作用域:将授权与任务挂钩,而非时间

修复方案的第一部分是意图绑定作用域(Intent-bound scopes)。其核心思路是生成一种能够指明其签发用途(即用户可见的任务)的授权,并让该授权在任务结束时失效,而不是根据一个随意的生存时间(TTL)来失效。目前有几项新兴标准正朝这个方向努力——OpenID 基金会的智能体身份(agentic identity)工作、IETF 的意图令牌(intent-token)原语草案,以及一系列厂商提供的能力令牌(capability-token)方案——但无论具体规范如何,其工程模式是相通的。

具体来说:当用户输入“总结这份 PDF 并通过电子邮件向我的团队发送一段摘要”时,智能体向 IAM 层发起的授权请求不应该是“在接下来的一个小时内给我 drive.readgmail.send 权限”。它应该是“给我一项能力,允许我读取该特定 URL 的文档,以及一项能力,允许我向该特定收件人列表发送一封具有该主题的电子邮件,两者均在任务完成或失败时失效”。能力令牌是与任务意图绑定的,而非与智能体的会话(session)绑定。

这也改变了审计的方式。令牌本身记录了用户指定的用途,这意味着审计日志终于可以回答“为什么”——不仅是“持有令牌 X 的智能体调用了端点 Y”,而是“代表用户所述任务‘总结这份 PDF 并将其发送给团队’的智能体调用了端点 Y”。如果提示词注入(Prompt injection)诱导智能体执行其他操作,那么该操作将显示为一个没有相应意图绑定的工具调用,而不是一个看起来与其他授权调用无异的普通调用。

更难的设计问题是谁被允许签发这些能力,以及智能体如何获取它们。最清晰的模式是令牌交换(token-exchange)步骤:智能体出示其会话令牌加上用户所述的任务描述,然后获取一个范围更窄的能力令牌。签发服务是一个位于 LLM 之外的策略执行点,这意味着 LLM 无法通过提示词注入来绕过它;模型可以申请能力,但它无法伪造能力。

记录“哪个提示词发起请求”的委托链

第二部分是使委托链(Delegation chain)显式化。在服务的 IAM 中,这种链条通常很浅:用户授权客户端,客户端调用服务。但在智能体场景下,每一次工具调用都是一个链条中的一环,该链条始于用户的提示词,经过模型的解析,并可能串联多个智能体和 MCP 服务器,最终才触达资源。审计日志需要重建这个链条,策略层也需要对该链条进行评估。

目前趋于一致的模式是结构化委托(Structured delegation),链条中的每一环都会产生一个令牌,其中指明了其父级、所授权的操作以及签发的上下文。每个子令牌必须是其父令牌的严格缩减版(Attenuation)——作用域更窄、过期更快、配额更小——并且资源服务器会在每次调用时验证整个链条,而不仅仅是末端的令牌。任何试图扩大作用域、增加配额或延长有效期的令牌都将无法通过加密验证。

审计价值是巨大的。当调查人员询问“为什么发送了这封邮件”时,委托链可以用具体的术语给出答案:用户说了这些,计划器将其分解为这些子任务,该子任务被委托给这个子智能体,该子智能体获取了这项能力,进而授权了这次工具调用。委托链还让询问更深层次的问题变得可行,例如“在此追踪记录中,是否有任何工具调用源自检索到的文档而非用户的提示词?”——这正是像“混淆代理”(confused-deputy)风格的提示词注入强迫你回答的问题,而扁平化的审计日志对此根本无法回答。

区分用户输入的意图与文档提供的意图

第三部分是在服务 IAM 中完全找不到类似物的策略区分:即“用户输入的一句话扩展成了这次工具调用”与“检索到的文档指示智能体调用此工具”之间的区别。服务 IAM 对指令的来源只字不提,因为服务在 IAM 相关的意义上没有“指令”;它们的行为是固化在代码中的。但智能体有指令,而指令的来源是关于工具调用的最关键的安全元数据之一。

在实践中构建这一点需要智能体运行时根据信任层级(Trust tier)标记每一个输入——用户提示词处于最高层,系统提示词次之,检索到的文档和工具输出位于底层——并在请求授权时将这些标签传播到策略决策中。如果一项能力需要用户层级的意图,那么当前活动指令来自文档层级来源的智能体就无法获取该能力。这与语言运行时中经典的污点追踪(Taint tracking)理念相同,只是应用到了自然语言指令这一新载体上。这也是目前已知唯一的结构化防御手段,用以对抗今天 MCP 集成中轻而易举实现的间接提示词注入。

这种方案的现实版本并不完美。智能体通过重新总结自身上下文可能会“洗掉”污点,而意志坚定的攻击者可以通过社会工程手段诱导用户重新输入恶意指令,从而跨越边界。设立边界的目的并不是为了让其坚不可摧,而是为了让 IAM 层意识到这种区分值得捍卫,就像 Web 平台在经历了多年 XSS 痛苦之后,学会了区分“用户输入”与“受信任输入”一样。

架构实现

总结起来很简单。OAuth、IAM 角色和 API 密钥是围绕一个在请求令牌之前就确定了其意图的调用者而设计的。智能体在请求过程中根据 IAM 层从未见过的输入来构建其意图,而授权层一直在基于一种已不再符合现实的意图模型悄悄做出决策。解决方法并不是抛弃 OAuth —— 加密底层、发现机制和资源服务器模式仍然是有用的。解决方法是在其上增加智能体时代所需的层级:随任务结束而失效的意图绑定作用域、记录来源的委托链、标明用户可见用途的能力令牌,以及一个能够识别哪些输入允许请求哪些权限的策略层。

搞错这一点的团队写出的事故报告都会如出一辙。令牌准确地执行了它被授权的操作,但它所做的工作却不是用户要求的。IAM 层会显示正常。但行为是错误的。这种“已授权”与“预期意图”之间的鸿沟就是新的防御边界,而这正是未来十年智能体安全工作的重心所在。

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