别让 Agent 越权委派:用 Aroha Mandates 把授权范围一路收窄
当 AI Agent 替用户调用工具,问题不只是“它能不能拿到 API key”,还包括:它把任务交给另一个 Agent 后,用户原本设定的金额、用途和时限是否还跟着任务走?每一跳都单独配一组凭据,往往难以表达整条委派链的权限边界。Aroha 是一个 Apache-2.0 许可的 TypeScript 项目,提出用可验证的 Mandate(授权凭证)把授权约束沿委派链传递,并让后续 Agent 只能缩小而不能扩大权限。
它不是 MCP 或 A2A 的替代品。项目 README 把它定位为可与这些协议及现有身份提供方配合的授权层:发起者签发授权,Agent 将授权范围收窄后再委派,接收方验证签名链和约束。这个思路适合需要跨服务委派、且希望审计“谁授权了什么”的系统;它本身并不会自动解决密钥管理、业务风控或部署安全。
Mandate 如何沿链传递
README 展示的基本流程分为三步:
- 用户为首个 Agent 签发
IntentMandate,其中可包含支出上限、商户类别和有效期等约束。 - 首个 Agent 可把授权进一步委派给子 Agent,但新授权只能是原授权的子集,例如将 500 美元上限收窄至 300 美元。
- 执行方调用
verifyMandateChain检查凭证签名链、授权根和最终接收者;链条被篡改、越权扩大或过期时应拒绝执行。
关键不是把一个 bearer token 传来传去,而是让授权链携带可验证的范围。项目 README 的示例使用内容寻址的 did:aroha:z… DID,并说明验证可离线完成;但撤销传播和实际请求中的凭证传输仍要按具体集成文档配置,不能把“可离线验证”理解为“任何环境都无需同步状态”。
最小验证示例
下面沿用仓库 README 中的 API 名称,演示在请求处理逻辑中验证授权链。HUMAN_ROOT_DID 应由应用可信配置提供,MY_DID 是当前服务自身的 DID;不要从请求正文中直接接受预期根身份。
import { verifyMandateChain } from "@aroha-sdk/credentials";
const { valid, chainVerified, reason, mandate } = await verifyMandateChain(
credentialToken,
undefined,
{
expectedRoot: process.env.HUMAN_ROOT_DID,
expectedGrantee: process.env.MY_DID,
},
);
if (!valid || !chainVerified) {
throw new Error(`Mandate rejected: ${reason}`);
}
// 仍需在业务层根据 mandate.constraints 执行对应限制。
console.log(mandate?.constraints);
这是授权验证的核心片段,不是完整可运行的 HTTP 服务:credentialToken 的来源、变量加载和异常映射由应用负责。README 说明 MCP 场景可通过 _meta["aroha/mandate"] 携带凭证,并提到 MCP bridge;不要自行假设存在某个 HTTP header。集成时应以对应 adapter 文档为准,并确认实际接收到的 mandate 与签名主体都经过校验。
落地时别忽略的边界
- 把本地策略与链上授权分开。 README 还列出 policy 包,用于在模型看到请求前执行确定性限制;授权链验证不能替代工具自身的参数校验和业务规则。
- 明确撤销语义。 项目描述了撤销及 witness 推送机制。若系统要求“撤销立即生效”,必须验证服务离线、网络延迟和缓存条件下的行为,不能仅凭协议描述假定所有验证节点即时获知撤销。
- 限定根身份来源。 根 DID 必须绑定到你的认证与授权流程。若调用方可任意传入
expectedRoot,验证结果就失去安全意义。 - 审计拒绝与成功路径。 README 提及允许和拒绝操作都会产生签名 receipt。接入后要核验日志保存、隐私保留期限和故障处理,避免审计记录本身泄露敏感数据。
对单体、单 Agent 的小工具而言,这层机制可能增加不必要的复杂度;但当 Agent 会跨组织或跨服务委派任务时,“权限随委派逐步收窄”比单纯传递 API key 更容易表达安全边界。更稳妥的试用方式,是先在无真实资金与生产凭据的沙箱里验证签发、衰减、拒绝和撤销四条路径,再考虑接入实际工具。