← 全部文章

Agent 权限控制:哪些动作可以自动做,哪些必须确认?

模型可以提出动作,但不能给自己授权。本文从身份、资源、策略、批准和隔离五层,建立可执行、可审计的 Agent 权限边界。

Agent 权限控制:哪些动作可以自动做,哪些必须确认?
文章目录
  1. 导语
  2. 一、先分清五个经常混在一起的概念
  3. 1. 身份认证:你是谁
  4. 2. 资源授权:你能操作什么
  5. 3. 运行时策略:在当前条件下是否允许
  6. 4. 人工批准:这一次是否放行
  7. 5. 隔离:即使出错,最多能影响哪里
  8. 二、为什么“读、写、危险”不够
  9. 三、工具能被模型看到,不等于它能被执行
  10. 四、一次权限判断到底需要什么输入
  11. 为什么返回三种结果
  12. 五、一个不过度绑定场景的执行骨架
  13. 权限检查必须靠近执行点
  14. 工具不能继承平台的万能凭证
  15. 拒绝也要成为可理解的观察结果
  16. 六、哪些动作适合自动执行
  17. 七、哪些动作通常值得人工确认
  18. 八、前端和后端分别负责什么
  19. 前端:让边界可见,但不充当安全边界
  20. 后端:在每个真实入口强制执行
  21. 九、用一个例子检查是否真的理解
  22. 十、常见误区
  23. 把工具风险写进 Prompt 就算完成权限控制
  24. 认为 read 天然安全
  25. 让模型提供 userId、tenantId 或 role
  26. 批准一次后永久放行
  27. 先检查权限,等待很久后直接执行
  28. 有策略检查就给工具管理员密钥
  29. 十一、本篇总结
  30. 参考资料

导语

上一篇讨论 Planning 时,我们留下了一个重要边界:

计划说明 Agent 想做什么,却不能证明它有权这样做。

假设模型生成了下面的工具调用:

{
  "name": "deploy_service",
  "arguments": {
    "service": "checkout-api",
    "environment": "production"
  }
}

这只表示模型提议部署生产环境,不表示部署已经获得许可。真正执行前,系统至少还要回答:

  • 当前用户是谁,他能否操作这个服务?
  • 这次会改哪个环境,影响多大,能否回滚?
  • 组织策略允许自动执行,还是要求人工批准?
  • 执行进程实际拿到了什么凭证和网络能力?

这些问题不能交给模型凭自然语言自我约束。它们要由模型之外的确定性系统检查。

这一篇,我们把 Agent 的“聪明”与系统的“权力”分开。


一、先分清五个经常混在一起的概念

1. 身份认证:你是谁

登录态、服务身份、租户信息回答的是 Authentication 问题。例如当前请求来自用户 u_123,属于租户 tenant_a。

这些信息应来自应用服务器验证过的会话或令牌,不能相信模型从对话中提取的“我是管理员”。

2. 资源授权:你能操作什么

Authorization 判断这个身份对具体资源是否拥有具体动作权限:

用户 u_123 能否对 tenant_a 的 checkout-api 执行 deploy?

这里的关键不是“能否调用 deploy 工具”这么粗,而是动作、资源和范围的组合。

3. 运行时策略:在当前条件下是否允许

用户可能有部署权限,但组织仍规定:生产环境只能在发布窗口操作,或金额超过一万元必须复核。

这类规则关注当前环境、参数、风险和业务状态。

4. 人工批准:这一次是否放行

权限通过不等于一定自动执行。对有明显外部影响、难以撤销或责任敏感的动作,系统可以暂停并请有资格的人批准。

批准不是补发权限。无权访问资源的人,即使点击“同意”,也不应让动作越权执行。

5. 隔离:即使出错,最多能影响哪里

最小权限凭证、文件系统沙箱、网络白名单、速率限制和资源配额,限制的是执行器真正能触达的范围。

权限判断可能有缺陷,工具也可能有漏洞。隔离层负责缩小最坏结果。

把这五层连起来,可以得到一条更准确的控制链:

流程图

模型位于这条链的起点,不是裁判。


二、为什么“读、写、危险”不够

很多系统会把工具标成 read、write、dangerous。这种标签有助于快速形成风险直觉,但不能直接当授权策略。

同一个读取动作,风险可能完全不同:

动作 看起来 实际差异
读取公开产品文档 read 通常影响较小
读取某客户合同 read 涉及租户隔离和敏感信息
导出全部员工资料 read 没有写入,却可能造成严重泄露

同一个写入动作也不一定都要确认。把草稿保存到个人工作区,通常可以自动完成;向十万名客户群发邮件,则需要更严格的控制。

所以风险至少与这些因素有关:

  • 主体:谁发起,代表个人还是服务账号;
  • 动作:读取、修改、删除、发布、付款等;
  • 资源:个人草稿、生产服务、客户数据等;
  • 范围:一条记录还是全量数据;
  • 可逆性:能否撤销,恢复代价多大;
  • 外部影响:是否通知他人、花钱或改变生产状态;
  • 环境:开发、测试还是生产;
  • 时间与上下文:是否在允许的窗口内,是否来自可信网络。

工程上可以保留粗粒度风险标签,供 UI 提示和默认策略使用;最终决定仍应结合真实参数和可信上下文。


三、工具能被模型看到,不等于它能被执行

这是 Agent 开发里很容易忽略的一层。

工具列表解决的是:模型现在可以提出哪些动作?

授权系统解决的是:这个具体调用现在是否允许执行?

两者可以共同收紧边界:

  1. 只向模型暴露当前任务可能需要的工具,减少误调用;
  2. 每次执行前仍做服务端授权与策略检查,防止越权。

不能只做第一步。模型看不到某个工具可以降低误用概率,却不是安全边界;攻击者仍可能绕过聊天界面直接请求后端接口。

也不能只把规则写进 System Prompt,例如:

不要在未经允许时部署生产环境。

这句话可以帮助模型少提不合适的动作,但它不是强制控制。通用型 Agent 的 System Prompt 更应该描述稳定的行为原则;具体用户、资源和临时审批状态,应由 Runtime 在每次调用时提供并校验,而不是塞进一个针对单场景过拟合的 Prompt。


四、一次权限判断到底需要什么输入

可以把策略输入整理为下面的结构。字段名只是本文示例,并非行业统一标准。

type ActionRequest = {
  actor: {
    userId: string;
    tenantId: string;
    roles: string[];
  };
  action: {
    toolName: string;
    arguments: unknown;
  };
  resource: {
    type: string;
    id: string;
    environment?: "dev" | "staging" | "production";
  };
  context: {
    runId: string;
    requestedAt: string;
  };
};

type PolicyDecision =
  | { outcome: "allow"; constraints?: Record<string, unknown> }
  | { outcome: "deny"; reasonCode: string }
  | { outcome: "require_approval"; reasonCode: string };

注意各字段的来源:

  • actor 来自服务端验证过的身份上下文;
  • toolName 和 arguments 来自模型提出的调用,经 Schema 校验;
  • resource 由应用代码根据参数解析,并在服务端查证;
  • context 由 Runtime 生成;
  • PolicyDecision 由确定性策略代码或专门的策略引擎产生。

模型可以辅助做风险分类,但不应凭自己的分类结果给自己放行。

为什么返回三种结果

allow、deny、require_approval 分别表示:

  • 系统已经有充分规则可以自动放行;
  • 当前动作不被允许,用户确认也不能绕过;
  • 动作在权限范围内,但当前策略要求由人承担这次决定。

这比一个布尔值更贴近真实产品,也为下一篇的 Human-in-the-loop 留出了明确接口。


五、一个不过度绑定场景的执行骨架

下面代码只表达控制边界,不绑定 PR Review、客服或部署等单一业务。

async function executeRequestedAction(
  call: ToolCall,
  trustedContext: TrustedContext,
): Promise<ToolResult> {
  const tool = toolRegistry.get(call.name);
  if (!tool) return failure("TOOL_NOT_FOUND", false);

  const parsed = tool.inputSchema.safeParse(call.arguments);
  if (!parsed.success) {
    return failure("INVALID_ARGUMENTS", false, parsed.error);
  }

  const request = await buildActionRequest({
    tool,
    arguments: parsed.data,
    trustedContext,
  });

  const decision = await policyEngine.evaluate(request);

  if (decision.outcome === "deny") {
    await audit("action_denied", request, decision);
    return failure(decision.reasonCode, false);
  }

  if (decision.outcome === "require_approval") {
    const pending = await savePendingAction(request, decision);
    return { ok: false, status: "waiting_for_approval", pendingId: pending.id };
  }

  // 等待期间资源和权限都可能变化,真正执行前再次检查。
  await assertStillAuthorized(request);
  return tool.execute(parsed.data, leastPrivilegeCredentials(request));
}

这里有三个容易漏掉的细节。

权限检查必须靠近执行点

如果检查发生在计划生成时,而执行发生在十分钟后,资源所有者、订单状态或用户角色都可能已经变化。执行前要重新校验,避免“检查时可以,执行时已经不可以”的竞态。

工具不能继承平台的万能凭证

即使策略层判断正确,执行器拿着全局管理员密钥也会放大缺陷。更好的做法是使用用户委托凭证、短期令牌或按任务裁剪的服务身份。

拒绝也要成为可理解的观察结果

模型需要知道动作为什么没执行,才能改走安全路径。但返回内容不应泄露敏感策略细节。例如告诉模型“当前身份无权访问该资源”通常足够,不必暴露完整角色配置。


六、哪些动作适合自动执行

没有一张适用于所有公司的固定清单。更稳妥的判断是:当系统可以确定身份和资源范围,动作符合显式策略,影响有限且可以审计时,才考虑自动执行。

典型例子包括:

  • 在授权范围内读取非敏感资料;
  • 查询订单状态、测试结果或构建日志;
  • 在个人工作区创建可撤销的草稿;
  • 对临时目录做范围受限的转换;
  • 执行已有明确配额和速率限制的低影响操作。

“可自动执行”仍不代表无限执行。批量读取也要受分页、频率和数据范围限制。


七、哪些动作通常值得人工确认

当动作满足下面一个或多个条件时,人工确认往往有价值:

  • 会向外部人员发送、发布或提交内容;
  • 会产生费用、法律承诺或合规责任;
  • 会修改生产环境或共享数据;
  • 难以撤销,或撤销代价很高;
  • 影响范围由运行时参数决定,而且这次范围较大;
  • 模型的选择包含重要业务判断,系统无法用规则完全确定。

人工确认也不是越多越安全。如果用户每次都机械点击“允许”,确认会退化成没有信息价值的仪式。合理策略应尽量让低风险动作自动流转,把注意力留给真正需要判断的节点。

下一篇会进一步回答:确认界面要展示什么、Run 如何暂停,以及批准后如何安全恢复。


八、前端和后端分别负责什么

前端:让边界可见,但不充当安全边界

前端可以展示:

  • 当前 Agent 正准备执行的动作;
  • 目标资源、环境和影响范围;
  • 自动放行、需要批准或被拒绝的原因;
  • 动作执行后的结果与审计编号。

但隐藏按钮、禁用菜单都不能替代后端授权。

后端:在每个真实入口强制执行

后端需要:

  • 从可信会话获取身份和租户;
  • 校验工具参数并解析真实资源;
  • 执行授权与策略判断;
  • 使用最小权限凭证调用外部系统;
  • 记录提议、决定、执行结果和关联 Run;
  • 对并发、重试和过期批准做防护。

前端负责让人看懂,后端负责让规则无法绕过。


九、用一个例子检查是否真的理解

用户说:

把 checkout-api 部署到生产环境。

请不要背术语,按参与者重建过程:

  1. 模型根据任务和工具说明,提出 deploy_service 调用;
  2. Runtime 校验参数,并从服务端登录态取得用户与租户;
  3. 应用代码把服务名解析成真实项目和生产资源;
  4. 授权系统确认用户是否有部署该资源的权限;
  5. 策略发现生产部署需要人工批准,于是保存待批准动作,而不是调用部署接口;
  6. 批准后,Runtime 再检查权限、资源版本和发布窗口;
  7. 受限执行器使用短期凭证部署,并记录结果。

如果用户没有该服务的部署权限,第 4 步就应该拒绝。此时不能靠一个确认按钮把拒绝变成允许。

这个例子做了一个简化:真实组织还可能接入 RBAC、ABAC、策略即代码、变更审批和云厂商 IAM。实现可以不同,但“模型提议,可信系统授权,执行器受限”这条边界不应消失。


十、常见误区

把工具风险写进 Prompt 就算完成权限控制

Prompt 是行为引导,不是不可绕过的授权机制。

认为 read 天然安全

读取可能越租户、泄露隐私或造成批量导出。风险要结合资源和范围判断。

让模型提供 userId、tenantId 或 role

模型输出不是可信身份源。这些字段必须由服务端上下文注入。

批准一次后永久放行

批准应绑定具体主体、工具、参数、资源和有效期。范围过大的永久批准会悄悄扩大权限。

先检查权限,等待很久后直接执行

等待期间环境可能变化。执行前需要重新校验关键条件。

有策略检查就给工具管理员密钥

策略和最小权限执行是互补关系,不能互相替代。


十一、本篇总结

Agent 权限控制的核心不是给工具贴三个标签,而是回答一个完整问题:

一个已经验证的主体,在当前上下文中,能否以给定参数对某个具体资源执行这次动作?

请记住四个结论:

  1. 模型提出动作,不给自己授权;
  2. 工具可见性可以减少误用,但真正的安全边界在执行端;
  3. 权限、策略、人工批准和隔离解决不同问题;
  4. 决定必须绑定真实身份、资源和参数,并在执行前重新校验。

当策略返回 require_approval 时,Agent 不能继续偷偷执行,也不能丢掉当前进度。它需要暂停,把决策交给人,再从原位置恢复。

这正是下一篇的主题:Human-in-the-loop:如何设计用户确认、接管和反馈?


参考资料