AI Agent 的工具调用前授权

工具调用前授权会在 AI Agent 执行动作前检查权限,控制它能读什么、跑什么、改什么,以及循环何时停止。

TL;DR — 工具调用前授权,指的是 agent 已经决定要做某个动作,但工具还没真正执行之前,由 runtime 做一次硬检查。它回答的不是“模型想不想做”,而是“这个 agent 现在能不能读这份数据、能不能在这里跑代码、能不能改这个系统、还能不能继续循环”。MCP 负责把工具接进来,sandbox 负责把执行隔离起来,pre-action authorization 就是夹在两者之间的门禁。

工具调用让 agent 真正有用了。也让 agent 开始有了真实的破坏力。

很多事故并不复杂。不是高深的越狱,也不是特别精巧的 prompt injection。更多时候只是模型做了一个看起来合理、但时机不对的动作:读了任务范围外的文件,把 PR 开到了错误的仓库,对同一个浏览器动作重试了 80 次,或者让一段生成代码带着网络权限跑起来。

所以生产级 agent 不能只靠“工具列表”。它需要工具调用前授权。不是写在文档里的权限模型,而是每一次关键动作执行前,runtime 真的拦下来检查一次。

它应该放在什么位置

一次 agent 动作大概有 5 步:

  1. 模型判断下一步想做什么。
  2. runtime 把这个意图转成工具调用。
  3. 授权层检查工具、参数、身份、任务范围和剩余预算。
  4. runtime 在合适的环境里执行工具。
  5. 可观测系统记录发生了什么。

很多 demo 会直接从第 2 步跳到第 4 步。生产系统不能这么干。

MCP tools specification 解决的是工具如何暴露、如何被调用。这个标准很重要,但它不会替你判断:这个 agent、这个用户、这个 workspace、这个任务,现在到底能不能调用这个工具。这个判断必须放在 runtime 里。

你可以把它理解成 MCP 执行边界agent runtime 层 之间的一个具体落点。MCP 让工具可用,runtime 负责执行,pre-action authorization 判断下一次调用能不能越过边界。

问题不是“有没有这个工具”

“这个 agent 有没有 GitHub 工具”这个问题太粗了。

把 GitHub 工具拆开看,风险完全不同:

动作风险类型默认策略
列 issue读取仓库范围内允许
读文件读取任务 workspace 内允许
跑测试计算sandbox 内允许
生成 patch草稿允许,但只停留在本地
打开 PR外部状态变更需要策略或审批
push 到 main高风险变更默认拒绝
删除仓库不可逆直接拒绝

这些不能被混成一个权限。否则最后只会得到两种系统:要么 agent 什么都干不了,要么 agent 什么都敢干。

更合理的做法是把能力和上下文一起判断:

{
  "agent_id": "coding-agent",
  "task_id": "fix-login-test",
  "user_id": "u_123",
  "workspace": "repo:sandbase-demo",
  "tool": "github.open_pull_request",
  "arguments": {
    "repo": "sandbase-demo",
    "branch": "agent/fix-login-test",
    "base": "main"
  },
  "risk": "external_mutation",
  "budget": {
    "remaining_tool_calls": 8,
    "remaining_seconds": 420
  }
}

这才足够做判断。同一个工具,换了仓库,可能要拒绝。同一个仓库,分支不对,也要拒绝。任务预算已经耗尽,就应该停。这个动作已经被 maintainer 明确批准过,可以放行,但要记审计日志。

先做 5 个门禁

一开始不需要做一个巨大的权限系统。先把 agent 最常见的翻车点拦住就够了。

门禁检查什么典型拒绝
Scope gate这个任务能不能用这个工具support agent 想调用 deploy 工具
Argument gate参数有没有越界文件路径逃出 /workspace
Execution gate动作会在哪里执行生成的 Python 想要宿主机网络
Mutation gate会不会改外部状态agent 想发邮件或开 PR
Loop gate循环还在预算内吗同一个工具被重复调用太多次

大多数团队会从 scope gate 开始。真正容易踩坑的是 argument gate。

读文件工具本身看起来安全,直到参数变成 ../../.env。浏览器工具看起来安全,直到 URL 指向内部后台。代码执行工具看起来安全,直到生成脚本要求网络权限和挂载凭证。授权层不能只看工具名,必须看参数。

门禁要放在 sandbox 前面

Sandbox 仍然是底线。只要 agent 能运行生成代码,就应该跑在隔离环境里,有资源限制,有网络规则。Claude 的 Managed Agents 发布文章 也说明了同一个方向:长时间运行的 agent 需要 sandboxed execution、scoped permissions 和 tracing,这些应该是产品基础设施,而不是每个应用临时拼出来的胶水。

但 sandbox 不能替代授权。

Sandbox 回答的是:如果这段东西跑起来,影响范围有多大?

Pre-action authorization 回答的是:这段东西现在该不该跑?

两个都要有。明显不该执行的动作,在进入 sandbox 前就拒掉。允许执行的计算,也仍然放进 sandbox。这样才有两道防线:

  • policy 拦住明显错误;
  • isolation 兜住 policy 没拦住的错误。

我不太喜欢那种“直接给模型一个通用 shell,然后把安全全压到容器里”的设计。容器能限制爆炸半径,但它不知道 git push origin main 对当前任务是不是合理。这是 policy 的问题。

人工审批要少,但位置要准

一说授权,很多人会想到“每次工具调用都弹确认”。这会把产品体验毁掉。

正确做法是把审批收窄:

动作类型推荐默认值
读取限定范围内的数据自动允许并记录
在 sandbox 里跑计算预算内自动允许
生成文本或代码草稿自动允许,但保持 staged
修改外部系统需要策略或审批
不可逆动作必须人工确认

Agent 应该能自己完成大部分工作:看文件、跑测试、准备 patch、草拟客服回复、总结日志、生成报告。这些不应该每一步都打断用户。

审批应该放在“草稿变成外部状态”的那条线上。比如发出回复、合并 patch、部署生产、扣款、删数据。这个位置才需要人或更严格的策略介入。

Anthropic 的 Claude Code hooks 文档 也指向同一个模式:PreToolUse 这类 hook 的价值,就是让系统在工具执行前检查和拦截动作。产品形态可能不同,但模式很稳定:副作用发生前,先拦一下。

一个最小可用的策略模型

最早可以做得很朴素:

type Risk = "read" | "compute" | "draft" | "mutation" | "irreversible";

type Action = {
  agentId: string;
  taskType: "code" | "support" | "research";
  tool: string;
  risk: Risk;
  args: Record<string, unknown>;
  workspaceId: string;
  remainingSteps: number;
};

type Decision =
  | { allow: true; audit: boolean }
  | { allow: false; reason: string }
  | { allow: false; reason: string; approvalRequired: true };

const taskTools = {
  code: ["repo.read", "tests.run", "sandbox.exec", "pull_request.draft"],
  support: ["ticket.read", "docs.search", "reply.draft"],
  research: ["web.search", "page.fetch", "notes.write"],
};

export function authorize(action: Action): Decision {
  if (action.remainingSteps <= 0) {
    return { allow: false, reason: "step budget exhausted" };
  }

  if (!taskTools[action.taskType].includes(action.tool)) {
    return { allow: false, reason: "tool not allowed for task type" };
  }

  if (action.risk === "irreversible") {
    return {
      allow: false,
      reason: "irreversible action requires approval",
      approvalRequired: true,
    };
  }

  if (action.risk === "mutation") {
    return {
      allow: false,
      reason: "external mutation requires approval",
      approvalRequired: true,
    };
  }

  return { allow: true, audit: action.risk !== "read" };
}

这段代码故意很普通。重点不是发明一门 policy 语言,而是让每个工具调用都经过一个单一、可测试、可审计的决策点。

后面你可以把硬编码规则换成 Open Policy Agent、Cedar、Zanzibar 风格的关系检查,或者公司内部已有的授权服务。但接口最好保持不变:输入 action,输出 decision,然后写审计日志。

审计日志要记什么

没有审计的授权,最后会变成客服噩梦。用户问“为什么 agent 停了”,你不能只回答“policy 拒绝了”。

至少要记录这些字段:

字段作用
trace_id关联到这次 agent run
agent_iduser_id知道是谁代表谁在操作
tool 和标准化后的参数复现这次判断
risk解释为什么需要审批
decisionallow、deny 或 approval required
reason给人看的拒绝理由
policy_versionpolicy 改版后方便排查

这和 agent observability 是一件事。一次 trace 里应该能看到模型调用、它想调用的工具、授权决策、执行结果,以及可能的审批事件。否则你只能猜:到底是模型选错工具,policy 拦了,还是 sandbox 挂了。

最容易翻车的地方

最常见的失败不是放得太松,而是拦得太死。

团队上线了一个严格 policy,结果 agent 每 20 秒就让用户点一次确认。用户很快疲劳,开始闭眼批准一切。系统表面上有权限模型,实际上没有判断力。

解法是渐进式自治:

  1. 读取和 sandbox 内计算,默认自动允许。
  2. 草稿类产物保持 staged,不直接改外部状态。
  3. 只有外部 mutation 需要审批。
  4. 观察一段时间后,把反复安全的动作提升为 policy。
  5. 不可逆动作永远保留人工确认。

信任不是靠一句“我们的 agent 很安全”建立的,而是靠系统让安全路径比危险路径更顺手。

FAQ

工具调用前授权和 guardrails 是一回事吗?

不是。Guardrails 是更大的控制体系,pre-action authorization 是其中一个具体环节:工具执行前由 runtime 做决定。完整控制栈可以看 production agent guardrails guide

每个工具调用都要人工确认吗?

不需要。人工审批应该留给外部状态变更和不可逆操作。读取、sandbox 内计算、草稿生成,通常可以在范围和预算内自动执行。

MCP 会帮我处理授权吗?

不会。MCP 规范的是工具如何暴露、如何调用。具体某个 agent 能不能在某个 workspace 里用某个参数调用某个工具,还是 runtime 的责任。

授权检查应该放在哪里?

放在 runtime 里,位于工具选择和真正执行之间。不要把它藏在 prompt 里。模型可以解释意图,但执行策略必须由 runtime 强制执行。

结论

Pre-action authorization 最好的地方,就是它足够无聊。它把“模型想做某件事”,变成“系统在副作用发生前做一次明确判断”。

如果你在做生产级 agent,先实现 5 个门禁:scope、arguments、execution、mutation、loop。安全的工作尽量自动化,外部状态变更再审批。每一次判断都写日志。

这就是“agent 能调用工具”和“agent 可以持续、安全地调用工具”之间的区别。