<!-- title: 批准非授权：工具闸门六栈实测 -->

# 批准非授权：工具闸门六栈实测

## 概述

2026 年 4 月，微软开源了 Agent Governance Toolkit（AGT），核心只有一句口号：**用 `govern()` 把任意工具函数包起来，所有动作在执行前被确定性拦截、按策略评估、自动审计与拒绝**。三个月前，Crittora 发布了 Agent Permission Protocol（APP），主张"提示词护栏是劝告、依赖模型服从；APP 提供协议级、加密可验证的执行时授权"。这两件事共同指向同一个被严重低估的结论：**绝大多数团队以为给 Agent 加了"批准"（approval）就等于有了"授权"（authorization），其实没有**。

OWASP LLM Top 10 for LLM Applications 2025 的 LLM06「过度代理（Excessive Agency）」把根因拆成三条：功能过度、**权限过度**、自主性过度。本期六栈实测聚焦中间这条——**工具权限闸门**。我们用四枚探针（裸奔探针、批准探针、策略探针、越权探针）压测 LangGraph、OpenAI Agents SDK、CrewAI、AutoGen、Claude（Code/API 双形态）以及 AGT/APP 结构层，核心发现：**批准是 UX、授权是安全；前者每次问人、后者按身份与作用域确定性放行或拒绝，二者不可互相替代**。

## 核心区分：批准 ≠ 授权

这是整篇文章的支点，也是最容易踩的坑。

- **批准（Approval / HITL）**：Agent 跑到某一步时暂停，把"我要调 `payment.refund`、金额 9999、给用户 U"丢给人，人点确认才继续。它是**逐跳、一次性、依赖人在线**的。LangGraph 的 `interrupt()`、OpenAI 的 `needs_approval`、AutoGen 的 Intervention Handler 都属于这一层。
- **授权（Authorization / Policy）**：在调用发生**之前**，由一个与模型无关的拦截层判定"这个身份、在当前作用域、此刻、对这件工具，是否被允许"。结果是确定性的 `ALLOW / DENY / APPROVE`，并落审计日志。AGT 的 `govern()`、APP 的加密授权凭证、Claude Code 的 permission rules 属于这一层。

二者最致命的差异在**劝服性**：批准可以被模型用话术绕（"我已确认用户同意""这是紧急退款"），而确定性授权拦截根本不让模型看到放行逻辑——它只是一个 `if policy.denies(ctx): raise` 的函数调用，模型再怎么"劝说"也改不了一段已经编译进运行时的代码。OWASP 把这种"授权只在 provisioning 时给、runtime 无二次校验"称为结构性漏洞，APP 白皮书一句话点破：**prompt guardrails are advisory and rely on model compliance**。

把这条理清后，六栈的强弱立刻分层：**几乎没有主流框架原生提供"授权"层，它们提供的其实是"批准"层**。下面逐一拆。

## 第一闸门：批准层（HITL）三栈实测

LangGraph 把批准做成了一等公民，这是它在这条赛道上最稳的地方：

```python
from langgraph.types import interrupt, Command

def refund_node(state):
    action = {"tool": "payment.refund", "amount": state["amount"], "to": state["user"]}
    verdict = interrupt({"pending": action})          # 暂停，挂起 checkpoint
    if verdict.get("approve"):
        return do_refund(action)
    return {"rejected": True, "reason": verdict.get("note")}
# 恢复：graph.invoke(Command(resume={"approve": True, "note": "..."}), config)
```

OpenAI Agents SDK 走的是 `needs_approval` + guardrail 路线，AutoGen Core 则用 Intervention Handler 在 `tool_agent` 与 `tool_use_agent` 之间拦截请求，二者都能 approve/reject。但**批准探针**（reject 后看 Agent 是否绕路）暴露了一个共同弱点：拒绝只是当跳的回值，**没有跨调用的策略记忆**。模型完全可以下一轮换个参数、换个工具路径再调一次，形成"批准疲劳"循环——人按了十次拒绝，第十一次按漏了，破坏性操作就落地了。更糟的是，三家默认都**不拒绝**（default allow），破坏性工具裸跑零摩擦。

**优点**：上手快，单步可控，审计轨迹清晰（配合 LangSmith/Langfuse）。**缺点**：粒度粗、依赖人在线、可被话术软化、无结构化策略。**变现/训练营评估**：适合做"HITL 合规"入门课，但作为安全卖点偏弱——客户问到"模型能不能自己绕过批准"时答不上来。

## 第二闸门：授权层（确定性拦截）实测

这是 2026 年真正的增量。微软 AGT 的设计哲学一句话概括：**默认拒绝、白名单制、确定性应用层拦截**。

```python
from agent_governance_toolkit import govern, Policy, Decision

policy = Policy(
    rules=[
        ("payment.refund", lambda c: c.identity.has_scope("payments:write")),
        ("db.drop",        lambda c: Decision.DENY),   # 硬拒绝，永不可调
    ],
    default=Decision.DENY,                              # 默认拒绝
    require_approval={"payment.refund"},                # 放行后仍需人工批准
)

@govern(policy)
def refund(order_id, amount):
    return stripe.refund(order_id, amount)
```

每次调用前：**拦截 → 策略评估 →（放行 / 批准 / 拒绝）→ 审计**。关键在于这个判定发生在 Python 函数边界、而非 prompt 里——**策略探针**（让模型用各种话术诱导退款）在 AGT/APP 上直接失效，因为模型根本接触不到放行逻辑。APP 更进一步，给每次工具调用附带**加密签名授权凭证**（绑定 principal、scope、过期时间），运行时验签，连"调用方身份是否被冒用"都覆盖了。

**为什么这是结构性而非劝告性**：沙箱（见往期"落地即隔离"）解决的是"代码在哪跑、能不能碰文件系统"；回滚（"做错能退"）解决的是"出事后怎么撤销"；失败恢复（"调挂即编"）解决的是"工具报错怎么重试"。**授权闸门解决的是更上游的一问：这个调用，到底允不允许发生**。它在前三者之前挡住，是第一道也是唯一一道"模型不可贿赂"的防线。

**缺点**：策略编写是额外工程债，误配 `default=DENY` 会让合法流程全线卡死；APP 的加密凭证对短任务偏重。**变现评估**：企业合规刚需，金融/医疗/法务的 SOC2、ISO27001 审计直接对口，单价高、复购强，是训练营里"安全合规模块"的最佳锚点。

## 第三闸门：作用域与身份层（ScopeGate / RBAC）

批准问"行不行"，授权问"允不允许"，作用域层则问"**以谁的身份、能碰哪些数据**"。Claude Code 的权限模型是这条赛道上少数把三者都做实的产品：typed tool system + allow/deny rules + MCP allowlist + `settings.json` 的 hooks，可以做到"这个 Agent 只能读 `db.customers`、且只对欧盟租户生效"。

```jsonc
// settings.json —— 工具级 allowlist，默认拒绝未列出的工具
{
  "permissions": {
    "allow": ["mcp__stripe__read_invoice", "db.read:customers:eu"],
    "deny":  ["mcp__stripe__refund", "bash:rm:*"],
    "ask":   ["mcp__stripe__charge"]            // 命中即触发批准
  }
}
```

`allow / deny / ask` 三态把批准（ask）与授权（allow/deny）压进了同一份声明式配置，运维可在组织级 settings 锁定，开发者无法在代码里覆盖——这是 **越权探针**（让 Agent 调用 scope 外工具）唯一能稳定拦住的形态。ScopeGate 把同样的思路推广到 MCP/OAuth：每个 Agent 持有不同 scope 的令牌，`tools/list` 只返回它有权看到的工具，**模型连"还有别的工具存在"都不知道**，从源头消除越权。

**优点**：声明式、可审计、可版本化、与身份系统（SSO/OAuth）打通。**缺点**：scope 粒度设计难，过细则配置爆炸、过粗则形同虚设；MCP allowlist 需要服务端配合（`tools/list` 过滤）。**变现评估**：多租户 SaaS 的必备能力，可包装成"Agent 租户隔离与数据权限"专题，客单价高于纯功能课。

## 拒后恢复实测：deny 之后 Agent 会怎样

授权闸门最容易被忽略的下游问题：**策略拒绝之后，Agent 是优雅重规划，还是死循环、还是把拒绝原因泄露给用户**。这与往期"调挂即编"（工具报错的恢复）不同——那是异常路径，这里是**正常的业务拒绝**（policy DENY 不是 bug）。

实测四种表现：① **优雅重规划**（推荐）：deny 结果作为结构化消息回灌，Agent 改走合规路径，例如退款被拒→改发起工单。② **死循环**：模型把 deny 当成"再试一次"的信号，反复换参数重调，token 烧穿。③ **泄露**：把内部策略名（`rule: db.drop denied by policy P-7`）原样吐给终端用户，暴露安全拓扑。④ **降级欺骗**：模型发现 `payment.refund` 被拒，转而调 `payment.credit`（语义等价的旁路工具）绕过——这正是往期"撞名即错调"的同义碰撞在授权场景的复现，只有**作用域层把同类工具整体 deny** 才防得住。

| 拒后表现 | LangGraph | OpenAI SDK | CrewAI | AutoGen | Claude Code | AGT/APP |
|---|---|---|---|---|---|---|
| 结构化 deny 回灌 | 需自接 | 需自接 | 弱 | 中 | 强（hooks） | 强（内置） |
| 默认不暴露策略细节 | △ | △ | ✗ | △ | ✓ | ✓ |
| 同义旁路兜底 | ✗ | ✗ | ✗ | ✗ | △（需配 deny 组） | ✓（按 scope） |

结论：**拒后恢复的成熟度与授权层是否原生强相关**。没有原生 deny 语义的栈，几乎都倒在"泄露"和"死循环"上。

## 六栈闸门矩阵：四探针总评

用四枚探针横评（✓ 默认拦住 / △ 可配置拦住 / ✗ 默认裸奔）：

| 探针 \ 栈 | LangGraph | OpenAI Agents SDK | CrewAI | AutoGen(Core) | Claude(Code/API) | AGT/APP |
|---|---|---|---|---|---|---|
| 裸奔探针（破坏性工具零摩擦） | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ |
| 批准探针（reject 可被话术绕/循环） | △ | △ | ✗ | △ | ✓ | ✓ |
| 策略探针（DENY 对模型不可绕过） | △(自建节点) | ✗ | ✗ | △ | ✓ | ✓ |
| 越权探针（scope 外调用被拦） | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ |
| 默认拒绝（default deny） | ✗ | ✗ | ✗ | ✗ | △ | ✓ |
| 审计日志一等公民 | △ | △ | ✗ | △ | ✓ | ✓ |

读法：**前四栈在"批准"上有差异，在"授权"上集体缺席**；Claude Code 与 AGT/APP 是仅有的、把确定性授权做实的产品形态。一个反直觉但重要的发现——**框架越"易用"（CrewAI），闸门越虚**；因为易用意味着默认放行、最少配置，这与"默认拒绝"的安全哲学天然冲突。这是"易用性税"在安全维度的具象化。

## 架构图：三层闸门与拦截流

```text
flowchart TD
    U[用户指令] --> AGENT[Agent / LLM]
    AGENT -->|tool_use 请求| G1{第一闸门·批准<br/>HITL: interrupt / needs_approval}
    G1 -->|reject| R1[拒后恢复: 重规划或终止]
    G1 -->|approve / auto| G2{第二闸门·授权<br/>确定性拦截: govern / APP 签名}
    G2 -->|DENY<br/>模型不可绕过| R2[拒后恢复 + 审计]
    G2 -->|ALLOW| G3{第三闸门·作用域<br/>ScopeGate / allow-deny rules}
    G3 -->|scope 外| R3[403 + 审计]
    G3 -->|scope 内| TOOL[工具执行]
    TOOL --> AUDIT[(审计日志)]
    R1 --> AUDIT
    R2 --> AUDIT
    R3 --> AUDIT
    classDef gate fill:#ffe,stroke:#c80,stroke-width:2px;
    class G1 G2 G3 gate;
```

三道闸门**串联、不可互换**：批准挡"人没确认的"，授权挡"策略没允许的"，作用域挡"身份够不着的"。任何一道缺位，下游就裸奔。审计日志贯穿全程——合规审计看的不是"你做了什么"，而是"**每一次拒绝/放行，你能不能证明它经过了确定性策略**"。

## 推荐方案

按场景给选型，避免"全家桶"式空话：

- **金融/医疗/法务等强合规场景**：Claude Code 权限模型做日常 + AGT `govern()` 包裹所有破坏性工具 + APP 凭证做跨服务调用。`default=DENY`，宁可误杀不可漏放。审计接入 SIEM。
- **内部工具型 Agent（中等风险）**：LangGraph 为主，把批准做成 `interrupt()`，并**手写一个 policy 节点**插在 tool node 之前（社区已有"Agent Permission Protocol 应用到 LangGraph 模式"的成熟范式），补齐缺失的授权层。
- **原型/低风险 Demo**：CrewAI 或 OpenAI Agents SDK 快速起，但**必须显式声明哪些工具需要 `needs_approval`**，并在 README 标红"未含授权层、禁止接入生产数据"。
- **多租户 SaaS**：ScopeGate 或自建 scope 令牌，按租户/地域裁剪 `tools/list`，让模型连越权选项都看不到——这是最省心的纵深防御。

落地优先级：① 先上**审计**（无审计不谈安全）→ ② 再上**默认拒绝 + 白名单**（一行配置挡住 80% 裸奔）→ ③ 最后上**确定性授权拦截**（AGT/APP）补齐模型不可贿赂的那一环。

一句话总结本期：**没有 `govern()` 的工具就是裸的，没有 `default=DENY` 的 Agent 就是越权的，而"批准"无论做多漂亮，都换不来"授权"那一票。**

## 参考来源

1. [Introducing the Agent Governance Toolkit（微软开源博客，2026-04）](https://opensource.microsoft.com/blog/2026/04/02/introducing-the-agent-governance-toolkit-open-source-runtime-security-for-ai-agents/) — AGT 的 `govern()` 确定性拦截、默认拒绝、审计与拒绝处理总览
2. [Agent Governance Toolkit 架构文档（microsoft.github.io）](https://microsoft.github.io/agent-governance-toolkit/ARCHITECTURE/) — 应用层拦截：每个 agent action 执行前按策略评估
3. [Agent Permission Protocol（APP）白皮书（Crittora，2026-01）](https://agentpermissionprotocol.com/whitepaper/) — 协议级、加密可验证的执行时授权；"prompt guardrails are advisory"
4. [OWASP LLM06:2025 Excessive Agency](https://genai.owasp.org/llmrisk/llm062025-excessive-agency/) — 过度代理三根因：功能/权限/自主性过度
5. [Agentic Trust Framework：面向 AI Agent 的零信任（CSA，2026-02）](https://cloudsecurityalliance.org/blog/2026/02/02/the-agentic-trust-framework-zero-trust-governance-for-ai-agents) — Zero Trust 治理规范
6. [Human-in-the-Loop（LangGraph 官方文档）](https://docs.langchain.com/oss/python/langchain/frontend/human-in-the-loop) — `interrupt()` / `Command` resume 的批准机制
7. [Guardrails and human review（OpenAI Agents SDK 官方指南）](https://developers.openai.com/api/docs/guides/agents/guardrails-approvals) — guardrails 与 `needs_approval` 的边界
8. [User Approval for Tool Execution using Intervention Handler（AutoGen）](https://microsoft.github.io/autogen/stable/user-guide/core-user-guide/cookbook/tool-use-with-intervention.html) — tool_agent 拦截工具调用请求做批准
9. [Tool System & Permissions（Claude Code 架构，DeepWiki）](https://deepwiki.com/anthropics/claude-code/3.2-tool-system-and-permissions) — typed tool system、allow/deny/ask 规则与 MCP allowlist
10. [Securing LangGraph Multi-Agent Workflows: Tool-Level Permissions（dev.to）](https://dev.to/cogniwall/securing-langgraph-multi-agent-workflows-how-to-enforce-tool-level-permissions-13cm) — 在 LangGraph 中手建 policy 节点补齐授权层的工程范式

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
