别再给 AI 智能体永久权限:建立任务到 Scope 的权限契约
面向创始人的实用框架:把 AI 智能体任务转换为最小 OAuth 授权,并正确处理部分同意、权限升级、撤销与动作回执。
Cloudflare 刚刚为第三方 OAuth 客户端加入可选 scope。客户端所有者可以把部分申请权限标记为“可选”,用户也可以在同意页面取消勾选。最终签发的 access token 只包含用户实际批准的 scope。还有一个很重要的细节:某项权限是必选还是可选,只针对这一次 authorization request 里真正申请的 scope 判断,而不是把这个客户端注册过的全部权限一股脑展示出来。
表面看,这只是同意页面的一次小改进。对正在构建 AI 产品的创始人来说,它暴露的是更大的设计问题:智能体可能已经连接邮箱、日历、文件、部署系统、客户资料,或者一个提供几十种工具的 MCP server;但用户通常只要求一个边界明确的结果,比如“找出三个都有空的时间并起草邀请”“汇总这些发票”,或“发布这次已经批准的落地页修改”。很多产品却在首次连接时取得一份宽泛、长期有效的授权,然后默认它可以覆盖未来所有任务。
本文面向非技术创始人、AI app builder 用户和正在发布连接型智能体的小团队。读完后,你会得到一份任务到 scope 的权限契约、权限矩阵、完整的日程安排案例、部分授权处理规则、动作回执、失败测试和 48 小时落地计划。核心判断是:不要把“账号已连接”理解成智能体对所有技术上可执行的动作都拥有永久权限。每个任务都应被转换成能够完成它的最小资源、scope、有效期与审批集合;真正执行动作之前,还要再次核验实际获得的授权。
这是一套产品与安全框架,不代表 optional scope 能够消灭提示注入或权限滥用。合理设计 scope 可以缩小错误的后果,但它无法判断某个动作是否正确、用户是否真正理解含糊的权限名称,也无法保证模型准确解释了任务。
OAuth 同意流程发生了什么变化,又有哪些没有改变
Cloudflare 8 月 20 日发布的 optional scope 功能同时增加了两层控制。开发者配置客户端时,可以把 scope 标为必选或可选;在某一次授权流程中,用户可以取消客户端本次实际申请的可选 scope。如果一个客户端注册过很多能力,这次只申请其中两个,同意页面就只评估并显示这两个。客户端所有者不主动启用新配置,原有行为不会改变。签发的 token 会反映用户最终批准的子集。这意味着客户端换取 authorization code 后,必须读取实际获得的 scope,不能假定申请过的权限全部获批。Google 的细粒度权限指南也提出相同的实现要求:检查用户真正授权了哪些 scope;某项权限缺失时,禁用或调整对应功能。Cloudflare 的可选项默认仍是勾选状态,所以这个功能不会自动产生最小权限。申请哪些 scope、默认值与说明怎么设计仍由开发者决定,用户也可能不加细看就批准整套权限。
因此,这次发布带来的是机会,不是保证。好的智能体会只申请当前任务需要的权限,在获得更窄授权时仍能安全工作,并且只在任务真正进行到某一步时解释为什么需要额外权限。糟糕的设计仍然可以在首次连接时申请所有能力,再把几乎所有权限都标成“必选”。
这不只是 Cloudflare 的产品逻辑。Google 当前的 OAuth 指南建议采用增量授权:用户启用某项功能时再申请相应 scope;被拒绝后停用受影响的功能;只有用户明确再次发起该功能时,才结合上下文重新询问。跨平台都适用的产品原则是:同意应当跟随用户能理解的意图,而不是跟随集成所能拥有的最大能力。
先分清八个权限层,再设计同意页面
团队常用“权限”指代几种完全不同的控制。先把它们拆开,才能避免漂亮的同意页面掩盖不安全的执行路径。
- 任务(task)是用户要求产品实现的结果。“准备一份会议时间方案”是任务;“调用日历 connector”只是实现选择。
- 动作(action)是一次外部操作,例如列出日历事件、创建草稿、发送消息、部署构建、删除文件。
- Scope 是与 OAuth token 关联的权限标签,例如读取日历或写入事件。只有资源服务器确实执行检查,scope 才有意义。
- 资源(resource)是 token 可以使用的目标服务或受保护对象,可能是特定 MCP server、API、租户、账号、文件夹、项目或环境。
- 授权结果(grant)是用户真正同意的权限集合,它可能比客户端申请的更窄。
- 升级授权(step-up authorization)是任务走到需要额外权限的阶段时,再触发的一次同意流程。
- 审批(approval)是产品对某个具体待执行动作的确认。用户允许创建日历事件,不等于他批准邀请某位客户参加某个时间的会议。
- 回执(receipt)是对申请了什么、批准了什么、尝试了什么、外部状态实际发生了什么的持久证据。
为什么“账号已连接”不是正确的产品状态
一个“已连接”徽标压缩了太多信息。它可能表示用户几个月前只批准了读取权限;可能表示管理员授予了整个组织的访问;也可能表示 token 可以写入,但只限于某一个租户。还有一种情况是 refresh token 已被撤销,界面却依旧显示“已连接”。这个徽标无法说明当前任务是否需要该权限,更不能说明用户是否批准了眼前这个具体动作。
在 onboarding 阶段一次性索取宽泛授权,会稳定地产生四类问题。
第一,用户无法把当下请求与未来后果联系起来。“连接工作区”日后可能被解释为读取私密记录、修改基础设施或代表用户发消息。第二,一旦提示内容被污染或规划出错,错误会继承整份宽泛授权。第三,部分授权会沦为异常路径:产品可能崩溃、反复弹窗,或者悄悄跳过工作。第四,发生事故后团队无法解释,因为历史记录里只有“用户曾经点击允许”这一条事实。
MCP 生态把这种错位表现得尤其清楚。一个 MCP server 可以提供庞大的工具目录,而通用客户端未必理解每个工具在业务上的含义。当前 MCP authorization 规范要求客户端遵循最小权限原则,使用受保护资源元数据和 resource indicator,并通过 step-up 流程逐步增加 scope。规范也承认一个现实难题:通用 MCP 客户端可能缺少足够的领域知识,无法聪明地选择每项 scope。因此,这部分知识必须有一个明确归属——它应该写进产品的任务策略,而不是交给模型临场发挥。
任务到 scope 的权限契约
任务到 scope 的权限契约,是一份有版本的映射:把用户可见目标对应到允许使用的资源、动作、scope、有效期、审批、证据和失败行为。它更像产品层的编译器,把高层请求转换为可执行的权限计划,再交给确定性系统检查。
可以采用下面七步。
1. 规范化任务,但不要偷偷扩大任务
重新写清结果、目标和边界。“安排这场会议”可能被扩展成推荐时间、创建事件、邀请参与者、附加文件、发送后续消息。不要默默推断这些子动作。把真正必要的动作和只是方便的附加项分开。
2. 解析出准确资源
明确服务商、账号、租户、工作区、项目、文件夹和环境。OAuth scope 通常描述动作类别,不一定包含具体对象或租户。OAuth Resource Indicators 标准允许客户端声明 token 要用于哪个目标服务,授权服务器也可针对该资源缩减 scope。资源绑定可以避免原本为一个服务签发的“日历写入” bearer token 被另一个服务接受,但应用端可能仍需为具体团队或文件夹做更细检查。
3. 把任务拆成原子动作
分别列出读取、起草、写入、发送、删除、购买、发布和管理权限变更。prepare_and_send_campaign 这类组合工具把两个不同决策藏在一个名字下。优先使用名称与 schema 能直接说明外部效果的工具。
4. 把每个动作映射到可执行的最窄 scope
采用服务商真正提供的 scope 分类。API 只有宽权限时,不要虚构一个实际上不存在的细粒度标签。遇到无法避免的宽 scope,可用服务端资源限制、短 token 有效期、动作 allowlist、精确预览和人工审批补偿。
5. 划分初始、可选和升级权限
初始 scope 是启动任务所必需的;可选 scope 能改善结果但不是完成任务的前提;升级 scope 则用于后续影响更大的阶段。相比 onboarding 时同时取得读写权限,在用户批准精确草稿后再申请写权限,通常更容易理解。
6. 把审批绑定到准备产生的效果
执行有后果的动作前,展示接收者、对象、字段值、目标位置、成本或 diff。审批记录应包含待执行载荷的 hash 或稳定标识,以及它所基于的版本。载荷一旦发生变化,原审批就应失效。
7. 核验实际授权并记录结果
token 换取完成后,对比申请 scope 与实际 grant。每次工具调用前,再检查身份、资源、scope、token 状态、任务状态和审批。保存服务商返回的结果 ID,并观察动作后的真实状态。产品承诺“会议已经创建”“消息发到正确会话”或“部署的是获批版本”时,一个成功的 HTTP 响应还不够。
可复用的产品权限矩阵
每个原子动作占一行。这张表应该出现在产品需求与验收测试里,而不只是安全文档里。
| 任务阶段 | 外部动作 | 资源边界 | 最小 scope | 同意时机 | 是否单独审批 | 所需证据 | 被拒绝时的安全结果 |
|---|---|---|---|---|---|---|---|
| 查找 | 读取空闲时间 | 单个日历账号 | 日历 availability read | 开始安排时 | 否 | 查询时间、账号 ID、结果数 | 请用户手动提供时间 |
| 起草 | 在产品内生成邀请预览 | 仅本地工作区 | 无 | 无 | 否 | 草稿版本 | 继续保存在本地 |
| 提交 | 创建日历事件 | 已选日历 | Event write | 时间确定后 | 是 | 标题、时间、嘉宾、实际 grant、event ID | 导出 .ics 文件 |
| 通知 | 发送外部消息 | 已选会话与账号 | Message send | 用户选择发送时 | 是 | 接收者、正文 hash、provider message ID | 复制草稿到剪贴板 |
| 附件 | 读取源文件 | 已选文件或文件夹 | File read | 用户添加附件时 | 视风险而定 | File ID、版本、分类 | 不带附件继续 |
| 恢复 | 删除误建事件 | 仅限这次创建的 event | Event delete 或 manage | 需要恢复时 | 是 | 原 event ID、删除结果 | 提供人工恢复说明 |
这张矩阵会暴露一种常见设计错误:因为完整流程最终会写入,所以团队把 write scope 认作“核心权限”。现实中,任务的很大一部分通常不需要 connector,或者只需要读取权限。先给出有价值的预览,用户就能在允许产生外部效果之前判断产品是否真的有用。
表格行数多并不代表更安全。如果服务商只提供 calendar.manage,就诚实写出这个宽 scope。矩阵要呈现“理想权限”和“真正可执行权限”之间的差距,让团队判断补偿控制是否足够。
完整案例:面对部分授权的日程安排智能体
假设一家两人创业团队正在构建客户成功助手。创始人提出:“看看下周 Maya 和我都有空的三个时间,起草一个续约规划邀请,等我选好以后再安排。”产品已经连接日历、邮箱、CRM 与文件服务。
不安全的实现会把四个已连接账号当成统一能力池:从 CRM 读取背景、搜索邮件、读取完整日历、寻找续约材料、创建事件、附上材料,再另外发送一条消息。原句并没有授权其中多项动作;如果 CRM 记录中藏有恶意指令,它还可能影响一个拥有长期写权限的计划。
任务编译器会生成更窄的方案:
- 如果存在多个 Maya,先让用户确认身份。
- 只为已选日历账号申请 availability-read 权限。
- 在本地计算三个选项,并明确显示使用的是哪个日历账号。
- 只根据用户提供的文字,在本地起草邀请。CRM 与文件访问并非必要,保持未授权状态。
- 创始人选定时间后,如果尚无 event-write 权限,再申请升级。
- 展示完整标题、参与者、时区、起止时间、说明、会议链接设置和提醒规则。
- 只有用户批准绑定后的准确载荷,才创建一个事件。
- 返回服务商 event ID 和直达链接,再读回事件,核验参与者与时间。
.ics 文件,或者以后需要时再允许创建事件。这就是良好的部分授权处理:产品保留已有工作的价值,但不跨越用户拒绝的边界。
反过来,如果用户允许写入却拒绝读取日历,应用也不应从无关邮件推断空闲时间,更不能悄悄切换账号。它可以请用户手动给出候选时间,然后创建获批事件。实际 grant 会改变可执行计划,但不能改变用户原本的意图。
动作回执:把权限证明与效果证明分开
OAuth 日志只能回答审计问题的一部分。产品回执需要把授权与准确任务、外部结果连接起来,同时不能保存 token secret。
task_receipt:
task_id: task_8f31
task_policy_version: scheduling_v3
user_intent: "推荐时间,然后创建一个获批事件"
resource:
provider: calendar.example
account_id: acct_142
tenant_id: team_7
authorization:
requested_scopes: [availability.read, events.write]
granted_scopes: [availability.read, events.write]
token_fingerprint: sha256:7b1...
issued_at: 2026-08-21T01:14:09Z
expires_at: 2026-08-21T02:14:09Z
action:
tool: events.create
payload_hash: sha256:9ad...
approval_id: approval_233
approved_by: user_81
approved_at: 2026-08-21T01:18:42Z
result:
provider_object_id: evt_901
observed_state: guests_and_time_match
verified_at: 2026-08-21T01:18:45Z
recovery:
method: delete_event_by_id
status: available
回执的保存期限应与风险和用户预期相称。只保存 token 指纹或内部引用,绝不保存原始 access token 或 refresh token。接收者、文件名、消息正文与账号 ID 都可能是敏感信息。回执用于问责,而不是在产品内部建立一份已连接工作区的影子档案。
对高风险动作来说,普通 scope 字符串可能过于粗糙。OAuth Rich Authorization Requests定义了结构化 authorization_details,可以表达访问类型、动作、位置或其他业务字段。各服务商的支持情况不同,结构化授权也不能替代服务端强制检查。它的价值在于说明:当产品和授权服务器都支持时,标准栈能够表达的不只是一列扁平标签。
Optional scope 解决不了的失败模式
所有权限都是可选,但默认全部勾选。 用户仍可能凭惯性批准整套权限。更好的做法是一开始就缩小申请集合,而不是要求用户从长清单里主动减掉风险。 Scope 描述工具,不描述任务。files.write 没有说明哪个文件、文件夹、租户或具体修改。还需要资源限制与准确动作审批。
客户端忽略部分授权。 它把申请的 scope 当成实际 grant,任务中途才失败,甚至调用被拒绝的工具。应当比较 token response,并提前设计清晰的降级路径。RFC 8707也再次强调:实际 scope 与申请值不同时,authorization server 必须在响应里返回 access token 的有效 scope。
模型自行选择权限。 被污染的文档可能说服 planner 把外发数据描述成“完成任务所必需”。模型可以提出动作,但从已批准任务类型到最大 scope 与资源的映射,必须由确定性策略控制。
长期 refresh token 超过任务寿命。 界面看起来按任务授权,底层却保留宽泛、长期有效的 refresh token,旧风险仍然存在。OAuth 安全最佳实践要求 refresh token 继续受用户同意的 scope 与资源约束,public client 还需要 rotation 或 sender constraint。产品侧也应先回答:这个任务真的需要 offline access 吗?
把撤销当成撤回。 系统正确执行时,撤销 token 可以停止未来调用;它不能召回已经发送的邮件、逆转已成交交易、恢复已删除记录或自动下线已发布部署。每类外部效果都需要单独定义补偿动作与升级路径。
资源服务器不执行 scope。 再漂亮的同意页面,也修不好一个会接受越权操作的 API。必须对真实后端运行负向测试。
Scope 名称无法理解。 用户无法从 objects.manage 做出有意义的决定。产品应使用普通语言说明任务效果,同时在可展开技术详情中保留 provider 的准确 scope 名称。
连接型智能体上线前的八项测试
- 最小集合测试: 对每个支持的任务,逐个移除申请 scope。如果任务仍能完整成功,这项 scope 就不是必需权限。
- 部分授权测试: 分别拒绝每项 optional scope,并测试不同组合。产品必须给出真实的降级结果,或者明确停止。
- 升级时机测试: 确认 write、send、publish、purchase 或 delete 权限只在用户真正走到相应动作时申请。
- 错误资源测试: 提供一个有效但属于其他 MCP server、账号、租户或环境的 token,后端必须拒绝。OAuth Protected Resource Metadata明确提醒,
scopes_supported清单不代表客户端应该申请所有 scope,并要求回到针对具体操作的最小化原则。 - 过期授权测试: 规划完成后、执行之前撤销 access 或移除 scope。工具调用必须停止,缓存的“已连接”状态不能覆盖当前 grant。
- 载荷变化测试: 用户批准事件后,再改变参与者、时间、金额、branch 或目标位置。原审批必须立即失效。
- 注入扩权测试: 在检索内容中放入一条要求调用无关工具或扩大 scope 的指令。策略必须拒绝扩张,或要求用户创建新的明确任务。
- 回执与恢复测试: 完成动作,确认 provider 对象与最终状态,再实际演练文档中承诺的撤销或补偿路径。
根据后果选择运行模式
并非每次连接都需要每个动作弹出一次同意。应根据后果、可逆性和用户预期选择模式。
| 模式 | 适合的工作 | 权限方式 | 必备证据 |
|---|---|---|---|
| 预览 | 公开信息检索、本地起草、格式处理 | 无 OAuth 或只读任务 scope | 来源与草稿标识 |
| 辅助执行 | 私密读取、创建可逆草稿 | 增量 read/create scope,短会话 | 实际 grant 与对象 ID |
| 确认后执行 | 发送、发布、邀请、修改共享状态 | Step-up scope + 载荷绑定审批 | 完整动作回执与最终状态 |
| 受限自动化 | 低变化、可明确限制的重复动作 | 专用资源、allowlist、预算、到期时间 | 每次动作回执、异常提醒、暂停控制 |
| 专业控制 | 资金移动、特权管理、受监管或安全关键动作 | 普通同意之外的领域控制 | 专业审查、职责分离、事故流程 |
如果服务商的权限分类远比任务宽、拒绝路径很差、没有撤销方法,或者团队无法核验真实效果,就应采用更低的模式。产品抱负不该用“消灭了多少个同意窗口”衡量。
这套框架适合什么,又在哪些场景下不够
任务到 scope 契约适合连接 SaaS API、MCP server、日历、消息系统、内容平台、代码托管、客服工具、分析服务和部署平台的智能体。一个 connector 同时提供读写操作,或者产品服务多个租户时,价值尤其明显。
对匿名公开数据、完全离线且没有外部效果的单用户工具,或一次性的本地转换来说,这套流程可能过重。即使如此,文件系统和操作系统权限仍可能需要处理;OAuth 并不是唯一的授权系统。
对于金融交易、支付、医疗决策、雇佣行动、关键基础设施、特权身份管理或法律承诺,这套框架并不足够。还需要交易限额、双人控制、职责分离、监管记录、专业人员审查与事故义务等领域措施。更窄的 OAuth token 可以缩小 blast radius,但无法证明策略、诊断、接收者、价格或决定本身正确。
如果服务商只提供一个极其宽泛的 scope,这套框架也无法凭空补出细粒度控制。此时可以使用专用低权限账号,在自己的服务后面限制资源,签发短期凭证,把高影响动作留给人工,或者放弃这项集成。
小团队的 48 小时实施计划
第 0–4 小时:盘点真实效果。 导出 connector 的工具与服务商 scope。分别标记读取、起草、写入、发送、删除、发布、支出、权限变更和管理动作,并记录各工具可能触达的租户与对象。 第 4–8 小时:定义三个优先任务。 选择用户最常用的连接型流程。写出预期结果、必需原子动作、可选增强、禁止扩张以及安全降级结果。 第 8–16 小时:建立矩阵与策略。 把每个动作映射到资源、可执行的最小 scope、同意时机、审批、证据和恢复方式。把最大允许计划写进模型提示词之外的代码或配置。 第 16–24 小时:实现部分授权。 读取有效的 granted scope set,从可执行计划中移除不可用工具,提供有用的降级模式和结合上下文的 step-up 流程。用户拒绝后绝不循环弹窗。 第 24–32 小时:增加动作绑定与回执。 对有后果的载荷做 hash 或版本化,将审批与载荷绑定,只保存 token 引用而不是密钥,捕获服务商 ID 并核验最终状态。 第 32–40 小时:运行八项失败测试。 使用一次性测试租户和假数据,覆盖已撤销 token、错误资源、提示注入扩权和审批后载荷变化。 第 40–48 小时:小范围上线。 从一个任务、一个 connector 和辅助执行模式开始。检查 scope 拒绝、放弃同意流程、step-up 失败、禁止工具调用、回执缺口和恢复事件。只有降级与事故路径都能工作,才继续扩大范围。创始人上线检查清单
- [ ] 用户可见任务与 connector 能力已被分别定义。
- [ ] 每个外部动作都对应准确资源与服务商的真实 scope。
- [ ] 初始申请只包含启动任务所需的 scope。
- [ ] Optional 与 step-up scope 都有诚实的降级结果。
- [ ] 客户端在授权完成后和动作执行前都会检查有效 grant。
- [ ] 模型无法自行扩大最大权限策略。
- [ ] 有后果的动作需绑定接收者、对象、字段值和版本后再审批。
- [ ] 可行时 token 应短期有效或可撤销;offline access 必须有明确理由。
- [ ] 回执不含 token 密钥,并设置与风险相称的保留期限。
- [ ] 后端负向测试证明错误 scope 与错误资源调用会被拒绝。
- [ ] 撤销权限与恢复外部状态被当成两个不同操作。
- [ ] 产品明确区分已完成、已降级、等待授权和失败。
参考资料
- Cloudflare:从全有或全无转向按任务授权
- Cloudflare:通过自动撤销、OAuth 与 scoped permissions 保护非人身份
- RFC 9700:OAuth 2.0 安全最佳实践
- RFC 8707:OAuth 2.0 Resource Indicators
- RFC 9728:OAuth 2.0 Protected Resource Metadata
- RFC 9396:OAuth 2.0 Rich Authorization Requests
- Model Context Protocol:Authorization
- Google OAuth 2.0 最佳实践
- Google:如何处理细粒度 OAuth 权限