Agent 不能借用用户令牌
做 Agent 接入时有一个非常自然的做法:用户登录了,把他的 access token 透传给 Agent,Agent 拿着它去调各个系统。省事、能跑通、演示效果很好。
它也是最危险的一种设计。因为从这一刻起,Agent 的所有行为都以这个用户的完整身份在跑——包括用户自己都没权限做的事。
1. 混淆代理:权限是谁的
经典的安全问题叫 confused deputy(混淆代理):一个有权力的组件,被诱导去执行调用者本来无权执行的操作。
Agent 天生就是这个角色。它有自己的服务身份(能读知识库、能发消息、能写工单),用户只有其中一部分权限。如果直接把两者混在一起,会出现两类事故:
越权。 用户没有审批权限,但他可以让 Agent "帮我走一下这个审批"。Agent 有权限,于是照做。系统日志里记的是"用户 A 审批通过"——而用户 A 根本没有这个按钮。
责任不清。 出了事,日志里只有用户身份,看不出是用户主动要求、还是 Agent 自己决定的。这在需要追责的场景里是致命的。
关键点是:Agent 的身份和用户的身份,是两个不同的主体,不能合并成一个 token。
2. 三条实现路径
代表用户(on-behalf-of)。 用户令牌不直接给 Agent,而是拿去换一个新的、范围更窄的、带双方身份的令牌。OAuth 体系里对应的标准是 RFC 8693(Token Exchange):用用户 token 换一个 act(actor)声明为 Agent 的 token。下游看到的是"用户 A 授权 Agent B 执行 X",而不是"用户 A"。
独立身份 + 资源侧授权。 Agent 有自己的 client credentials,用户在自己的账户里"授权这个 Agent 代表我"。每次调用带两个信息:Agent 身份 + 用户授权凭据。适合 Agent 长期驻留的场景(比如一个常驻的助手)。
平台托管(如 MCP 的企业托管授权)。 新版 MCP 规范里已经有 Enterprise Managed Authorization 这类扩展,把"企业策略 + 用户授权 + 服务身份"交给统一的授权服务器处理。适合多租户 SaaS 里的 Agent 接入,自己实现整套 token exchange 成本很高。
三条路的共同点:下游永远能同时看到"谁授权"和"谁执行"。只有其中一个,审计就做不完整。
3. scope 必须收窄
换到令牌不代表安全了。真正决定风险的是这个令牌能干什么。
三条约束:
只授予本次任务需要的 scope。 Agent 要读一份文档,就只要读这一份的权限,不要"读全部文档"。这要求下游支持细粒度授权(资源级、字段级),做不到就只能靠业务层过滤——但那已经是"信任 Agent 会自觉"了。
短生命周期。 代表令牌的有效期应该以分钟计,任务结束即失效。长期有效等于把一次授权变成了永久授权。
关键动作二次确认。 涉及写操作、对外发送、金额变动时,即使令牌允许,也要走一次人机确认(见《护栏要做在基础设施层》)。授权解决"能不能",确认解决"要不要"。
4. 五个会踩的坑
令牌进了上下文。 把令牌拼进提示词、写进工具参数、或者塞进对话历史,它就会被写进日志、可能被送给另一个供应商、也可能在下一次生成里被模型复述出来。令牌只应存在于传输层和受控的凭证存储里,不进任何会被模型看到的地方(这一点《密钥不能进浏览器》从浏览器侧讲过同一件事)。
长期令牌。 常驻 Agent 最容易犯的错:授权一次,令牌长期有效。用户以为自己授权的是"帮我处理这个月的数据",实际交出去的是一份半年有效的许可。正确做法是每次任务一次授权,任务结束即撤销,需要长期驻留时用可撤销的授权记录而非长期令牌。
Agent 之间互相借用。 主 Agent 拿到代表令牌后转交给子 Agent,链路上每一跳都在放大范围。多智能体场景里,每个子 Agent 都应当有自己的身份与自己的最小 scope(分工怎么划见《多智能体怎么分工》)。
工具可见性和授权不一致。 一个身份看不到某个工具,但被授权了能调它的令牌——这两套约束必须来自同一个决策。能看见才可调,看不见就不该被授权,否则界面上不存在的按钮仍然可以从模型侧触发。
把路线图当成已实现的能力。 面向"无人值守 Agent"的身份方案(把令牌绑定到密钥、跨云的工作负载身份联合、令牌交换)目前仍在推进中,尚未进规范;已经稳定的部分是企业托管授权那类扩展。设计时要按当前已实现的来,不要按明年会有的来。
5. 审计:四个字段缺一不可
一条合格的审计记录至少要有:
who_requested 用户 ID
who_acted Agent ID + 版本
what_authorized 本次令牌的 scope 与来源(哪次授权、何时过期)
what_happened 工具名、参数摘要、结果
只有第一个字段,等于把 Agent 的行为记在用户头上;只有第二个字段,等于不知道是谁让它做的。四个都有,才追得动。
顺带一个实践细节:Agent 版本也要记。同一个 Agent 改了提示词、换了模型,行为可能完全不同。日志里只有 Agent ID,排查时无法区分"是它变笨了"还是"这次任务本来就难"。
6. 落地清单
- 用户令牌不透传给 Agent,走 token exchange 或独立授权
- 代表令牌带
act或等价声明,下游能区分两个主体 - scope 按任务收窄,默认最小权限
- 令牌短有效期,任务结束即撤销
- 写操作与对外通信有二次确认
- 审计记录四个字段齐全,含 Agent 版本
- 授权链路可回放:能回答"这次操作为什么被允许"
最后一句:Agent 的权限设计,本质上是在回答"你愿意让它替你做多少事"。这个问题的答案不该由一行 passThroughToken = true 来决定。