设计安全的能力
本章所说的能力,是页面向外部 Agent 声明的一项业务操作。Agent 客户端通常把它呈现为一个工具,但工具描述只是入口;实际执行仍由页面完成。
因此,公开能力等于对外承诺:这个操作做什么、接受哪些目标和输入、执行前经过哪些检查。若操作本身含糊、无界或只依赖浏览器端授权,补一段工具说明不会使它安全。
假设你维护一个订单管理页面,页面上有查看订单、修改备注、审批和删除四个操作。它们在界面上可能都是按钮,但后果并不相同。下面的判断会围绕这组操作展开。
安全性不是能力单独具备的属性。同一个操作,对只处理一张工单的客服 Agent 可以接受,对匿名客户端或批量导出则未必。设计时必须说明谁在操作、作用于什么、范围有多大、会产生什么后果。
从业务动作开始
一个能力应对应应用已经理解并且能够执行的业务操作。「修改 SO-1002 的订单备注」是业务操作;「点击第 42 行的保存按钮」是渲染指令。前者可以跨越排序、滚动和组件重渲染,后者不能。
如果一个动作只能通过选择器、视觉位置或一组 DOM 事件准确描述,它还没有准备好成为能力。页面应先提供可复用的业务函数,或者让任务继续使用 DOM 自动化。把一次不确定的点击包进工具 schema,不会让执行结果变得可靠。
复合操作适用同样的判断。「保存并发布」包含两个后果不同的动作;只需要保存的 Agent 不应因此获得发布权。当目标、影响或确认要求不一致时,应拆成多个能力。
划定三条边界
每个能力都需要明确目标、输入面和后果。
| 边界 | 回答的问题 | 设计要求 |
|---|---|---|
| 目标 | 影响哪条记录或哪个页面对象 | 稳定的业务 id,并在当前会话和上下文中校验 |
| 输入 | 调用方可以传入什么 | 用 schema 限定字段、类型、允许值和范围 |
| 后果 | 可能改变什么,需要谁确认 | 如实描述 effect、确认策略和人工步骤 |
目标应是页面能够再次解析的业务对象,不是坐标或临时元素。例如,列表排序、筛选或重新渲染之后,SO-1002 仍然指向同一张订单;表格行号则可能已经改变。
没有稳定目标的能力,通常只是伪装成单次操作的批量操作。delete_rows 接受任意 id 列表,与 delete@order 只处理一条记录,即使最终调用同一个后端接口,风险也不相同。单记录版本可以裁剪、预览和审计;任意列表还需要额外约束。
输入面由能力的 inputSchema 描述,也就是调用方可以传哪些字段、哪些字段必填。它应表达业务契约,而不是接受一个通用命令对象。自由形式 payload: object 会接受页面从未设计过的值;note: string、status: "pending" | "approved" 或 delivery_date: date 能把操作限制在业务规则内。页面和后端仍要校验值,schema 只负责把可接受的形状说清楚。
后果指页面接受调用之后可能发生的事情,与 HTTP 方法或触发它的控件无关。后文“用 effect、approval 与 handoff 表达后果”会说明如何用 effect、approval 和 handoff 描述这些后果。
命名与作用域
使用稳定的业务意图标识,不依赖按钮文案、行位置或组件名。
read_order 读取一条订单
update_note@order 修改一条订单的备注
approve@order 审批一条订单
delete@order 删除一条订单@order 标记一组行内动作。能力清单只为这一组保留一个模板,不为每一行生成一条。具体目标来自当前上下文中的记录 id(context.recordIds),在调用时传入。这样,一百行可见数据不会产生一百个工具。
不要把用户生成文本、邮箱、DOM id 或随机渲染 key 放进能力 id。它们会变化、泄露信息,也会让审计记录失去稳定性。若两行具有不同的业务语义,它们需要不同的操作;若语义相同,一个模板即可。
作用域应跟随业务结构。客服页面可以开放订单查询和备注编辑,不必同时开放退款或账户注销。会话还可以更窄:某个 Agent 可以读取客户订单,但不能导出。这里的会话,指经过验证的用户、Agent、可访问能力范围和有效期之间的关系。页面级声明是上限,会话作用域是额外限制;二者都不能替代针对具体记录的授权。
收窄输入
当前能力 schema 使用对象形状的 inputSchema,通过 properties 和 required 描述字段。它的作用是拒绝业务无法支持的值,而不是在事后解释这些值。
有效的输入通常具备以下性质:
- 必填字段能确定操作和目标;
- 取值集合有限时使用枚举;
- 自由文本有长度限制;
- 不包含凭证、token 或密钥;
- 不包含选择器、脚本、SQL 或任意 URL;
- 不包含绕过服务端检查的客户端标志。
例如,备注更新应接受记录 id 和备注文本,不应接受 force: true、skip_approval: true,也不应接受含义取决于页面渲染方式的 HTML 片段。状态流转应接受 approved、rejected 这样的业务状态,不应接受含义可能变化的数据库数字值。
客户端不会因为提供了符合 schema 的输入而变得可信。页面校验目标和输入,后端重新检查权限、状态、归属和业务不变量。Schema 只收窄请求,不授予权限。
用 effect、approval 与 handoff 表达后果
前面所说的后果,在能力描述里由三个相关但不同的字段表达:
| 字段 | 取值 | 含义 |
|---|---|---|
effect | read、write、destructive | 操作可能产生的业务变化类型 |
approval | 省略、ask、always | 执行前是否必须获得确认 |
handoff | 存在时为 { reason } | 必须由人完成的步骤,Agent 不能替代 |
effect 描述预期的业务变化。read 表示操作不打算改变业务状态,不表示它返回的数据没有风险。导出可以在不修改任何记录的情况下披露客户信息。write 覆盖普通状态变更;destructive 用于后果难以撤销或恢复成本异常高的操作。
approval: "ask" 会在执行前询问用户,并允许同一动作形状在会话内记住授权。approval: "always" 每次调用都询问,不提供「始终允许」。省略 approval 表示描述中不要求额外确认,不表示后端已经授权。
handoff 不是更强一级的 approval。Approval 回答「Agent 是否可以做」;handoff 回答「这一步必须由人来做」。输入一次性验证码、选择本地文件、签署文件或完成支付确认都属于人工步骤。Agent 不能用自己的值代替人的操作。
组合方式应取决于后果。修改备注可以是 write + ask;审批高金额订单仍可以是 write,但使用 always;删除订单是 destructive,协议展示的确认之外还可能要求第二位审批人或人工复核。
因此,同一个页面的不同操作可以得到不同分类,而不需要改变界面结构:
| 能力 | effect | approval | 理由 |
|---|---|---|---|
read_order | read | 省略 | 返回单条受作用域限制的订单字段 |
update_note@order | write | ask | 修改可见备注,业务影响普通 |
approve@order | write | always | 提交高影响的状态流转 |
export_orders | read | always | 不修改记录,但会披露一组数据 |
delete@order | destructive | always | 难以恢复,且必须明确目标 |
读取并不自动低风险。返回整条客户记录、完整导出或其他用户数据的操作,即使不写入,也需要更窄的会话作用域和有界上下文。如果读取无法限制到任务需要的字段,就不应作为通用能力开放。
破坏性操作按例外处理
破坏性设计从目标和影响范围开始。单记录操作比批量操作更容易审查:页面可以解析一个对象、检查当前状态,并给出明确结果。批量操作需要显示数量、提供预览、设置硬上限,并单独取得人的决定。
不要把协议的 request.id 当作业务幂等。业务幂等指同一个业务操作重复提交时只产生一次效果,而不是多次扣款或多条记录。防重放保护只在配置的时间窗口内拒绝重复 request id,不能让一次财务或库存操作在窗口之外、传输失败之后或换用新 request id 后安全重试。业务后端必须实现自己的幂等、事务边界和状态检查。
业务允许时,优先拆成「准备」和「提交」两步。协议可以为每一步携带确认与结构化结果,但页面和后端必须定义「已准备」意味着什么、何时失效。通用的「删除所有选中项」不应成为工具。
不应成为工具的动作
有些操作无法靠增加确认提示变得安全,因为它们破坏了能力审查赖以成立的边界。
不应开放:
- 通用 DOM 选择、点击、脚本执行或浏览器控制;
- 任意 HTTP、SQL、shell、文件系统或代码执行;
- 原始凭证、会话 token、API key 或委托密钥;
- 为调用方 Agent 授予、撤销或提升权限;
- 绕过服务端授权或审批的客户端标志;
- 无限制的批量删除、批量导出或任意目标跳转;
- 能力描述没有说明的隐藏副作用;
- 只根据按钮文案或 DOM 结构推断、没有页面作者确认语义的写操作。
最后一项需要单独说明。框架可以根据按钮文案推导动作,并把动作提供给页面内助手。这种推导适合观察,但不是开发者写下的契约。source 与 fidelity 字段用于说明动作是声明还是推导、目标有多可信;它们不会把推导出的写操作升级成安全能力。
这些领域中的部分业务仍然可以有合法的人工流程。管理员可以授予角色,用户也可以授权支付;这些流程应设计为明确的页面与后端操作,而不是交给自主 Agent 的通用工具。
发布前检查
声明一个能力之前,回答下面的问题。任何一个「否」都是停止信号,而不是继续润色工具描述的理由。
- 能否不用选择器或屏幕位置,用一句话说明这个操作?
- 目标是否是页面可以解析并重新校验的稳定业务 id?
- 输入 schema 是否足够窄,能够拒绝业务不支持的值?
effect是否按真实后果分类,包括读取可能造成的数据披露?- 确认策略是否匹配后果,Agent 是否不能自行批准?它必须不能。
- 必须由人完成的步骤是否表示为
handoff,而不是由 Agent 模拟? - 后端是否独立校验权限、归属、状态和幂等?
- 操作在重复、重试和并发会话下是否安全?
- 返回的字段和记录是否限制在任务所需范围?
- 失败时是否关闭操作,而不是返回一个看似成功但未经验证的结果?
小结
安全的能力是一个边界明确的业务操作:目标稳定、输入受限、后果如实描述,并且有清楚的确认与人工步骤。协议字段把这些决定暴露给客户端,但不会替页面执行它们。页面校验、后端授权和业务幂等仍然是设计的一部分。

