第 21 章 AI AgentAgentic Patterns安全

第 21 章 权限与审批:谁有权干什么、谁点头

隔离完了,还剩"谁有权干什么"

第 20 章把"读"和"干"分开了。但权限问题还没答完:Agent 具体能调用哪些工具?哪些动作必须有人点头? 一个能访问工具服务器的 Agent,理论上能带任何参数调任何工具,中间没有任何强制层。这不行。

这一章讲四个模式,从"要人审"到"精确到动作"到"统一门禁"到"策略化授权":

  1. Human-in-the-Loop Approval Framework(人在回路审批):高风险动作插人工审批门。
  2. Exact-Action Authorization Binding(精确动作授权绑定):把审批绑定到完整动作,执行前重新比对。
  3. Policy-Gated Tool Proxy(策略门控工具代理):Agent 和工具之间插一个透明代理,每次调用过策略引擎。
  4. Sandboxed Tool Authorization(沙箱授权):模式匹配 + 默认拒绝 + 层级继承的策略。

模式一:人在回路审批(Human-in-the-Loop Approval Framework)

问题

自主 Agent 需要执行高风险或不可逆操作(数据库修改、生产部署、系统配置、API 调用),但让它们无监督执行,会带来不可接受的安全和合规风险。把所有操作都拦住又违背了 Agent 自动化的初衷。

方案

为指定的高风险函数系统化地插入人工审批门,同时保持安全操作的 Agent 自主性。 建轻量反馈循环,让"时间敏感的人工决策"能发生,而不阻塞整个 Agent 工作流。

核心组件:

风险分类:

  • 识别需要人工审批的函数。
  • 定义审批标准(成本阈值、数据敏感性、可逆性)。
  • 按风险级别给操作分类。

多渠道审批界面:

  • Slack 集成:实时通知。
  • 邮件:异步审批。
  • SMS:紧急/关键操作。
  • Web 仪表盘:批量审查。

审批工作流:

  • Agent 执行风险函数前请求许可。
  • 人类收到上下文丰富的审批请求。
  • 快速批准/拒绝/修改。
  • Agent 根据响应继续或调整。
  • 超时处理带可配置升级(推荐默认拒绝)。

审计轨迹:

  • 记录所有审批请求和响应。
  • 追踪谁在何时批准了什么。
  • 支持合规和调试。
Agent → 框架: 请求 DROP old_users 表
框架 → 分类: 高风险
框架 → Slack: 发审批请求(带上下文)
Slack → 人: 带批准/拒绝按钮的通知

批准: 人点击批准 → 框架授权 → Agent 执行 DROP → 记录审批和执行
拒绝: 人点击拒绝+理由 → 框架拒绝+理由 → Agent 调整计划(跳过或找替代)

证据

  • 证据等级:生产验证validated-in-production),来自 Dexter Horthy(HumanLayer)。
  • 学术支撑:Beurer-Kellner 等人(2025)把审批系统当成安全模式做学术处理,包括"提议和执行分离"。

怎么用

什么时候用:

  • 生产数据库操作(DELETE、DROP、ALTER)。
  • 有副作用的外部 API 调用(支付、邮件、webhook)。
  • 系统配置变更(防火墙规则、权限)。
  • 破坏性文件操作(批量删除、覆盖)。
  • 合规敏感操作(GDPR、HIPAA、SOC2)。

实现示例:

装饰器模式(HumanLayer):

from humanlayer import HumanLayer

hl = HumanLayer()

@hl.require_approval(channel="slack")
def delete_user_data(user_id: str):
    """删除用户所有数据,需要审批"""
    return db.users.delete(user_id)

# Agent 正常调用函数
delete_user_data("user_123")
# 执行暂停,审批请求发到 Slack
# 人工批准/拒绝后恢复

中断模式(LangGraph):

from langgraph.types import interrupt

def risky_operation(state):
    approval = interrupt({
        "question": "要我继续这个操作吗?",
        "operation": state["message"]
    })
    return {"user_approval": approval}

# 用 checkpointer 编译,保留状态
app = workflow.compile(checkpointer=MemorySaver())

配审批渠道:

approval_channels:
  high_risk:
    slack: "#agent-approvals"
    sms: "+1234567890"   # 紧急用
  medium_risk:
    slack: "#agent-review"
  low_risk:
    email: "team@company.com"

审批请求要带的上下文: 为什么需要这个操作?会影响哪些数据?可逆吗?有什么替代方案?

取舍

  • 好处:让高风险操作能安全自主执行;在最要紧的地方保留人工监督;集成轻量(Slack 按钮,不是复杂 UI);审计轨迹支持合规和调试;减少 Agent 对犯错的不安;信任可以随时间逐步扩大。
  • 代价:要求人类随时在线、响应及时;审批慢会卡住 Agent 工作流;基础设施复杂(通知系统、状态管理);有审批疲劳导致"橡皮图章"的风险;要清楚划分什么需要审批;频繁请求会打断人的专注。

模式二:精确动作授权绑定(Exact-Action Authorization Binding)

问题

审批系统常让人或策略服务批准一个提议的工具调用,然后稍后再执行。这中间,请求可能被重建、转换、重试、委派、或从存储状态恢复。于是一个看起来有效的批准,可能被用在一个实质不同的动作上:不同的接收者、目标、操作、载荷、执行者、执行面、或策略版本。

普通审计日志显示"批准了什么"和"执行了什么",但事后对比阻止不了不匹配。工具级白名单和人工审批门决定"动作能不能进行",但除非执行边界验证的是"被批准的精确动作",否则它们关不上这个"批准到执行"的 TOCTOU(时间检查到时间使用)缺口。

方案

把授权绑定到"提交审批的完整授权相关表示"的确定性身份上,并要求执行适配器在执行前立即重新推导这个身份。

  1. 定义授权相关字段:任务、批准人、执行者、执行面、操作、解析后的目标身份、参数、治理策略、过期时间、nonce 或一次性标识。
  2. 用文档化的 profile 规范化这个表示。算一个带版本、抗碰撞、域分离的摘要,把 profile 标识绑定进授权。
  3. 签发认证过的短期授权信封。签名或 MAC、受保护的不透明能力、或可信事务查找,必须绑定签发者、摘要、profile、过期时间、nonce、策略、预期强制面。
  4. 执行边界处,认证信封,从适配器实际要消费的不可变值重建动作。独立规范化并比对摘要。
  5. 执行前原子地 compare-and-consume 一次性授权。不匹配、过期、重放、完整性失败都拒绝。比对之后任何实质性转换都要新授权。
  6. 把授权匹配、执行尝试、独立观察到的外部效果记录成三条独立声明。摘要相等只关联记录,不证明执行发生过或成功过。
profile = "example.action-binding/v1+sha256"
approved = canonicalize(profile, {
  task_id, actor, acting_surface, operation,
  target, arguments, policy_id, expires_at, nonce
})
authorization = authenticated_approve(
  domain_digest(profile, approved), profile, expires_at, nonce
)

# 稍后,在真正执行动作的适配器里:
verify_envelope_integrity_and_issuer(authorization)
presented = canonicalize(profile, immutable_values_used_by_adapter())

if domain_digest(profile, presented) != authorization.action_digest:
    deny("被批准的动作变了")

atomic_compare_and_consume(authorization)  # 过期、重放、竞态都失败
result = execute(presented)
record_attempt(authorization.id, result)

适配器是强制点。 一条"只执行被批准的"的提示词指令不是这个模式,因为同一个模型可以重新解释批准和动作两者。适配器原子消费后崩溃,用幂等键或权威状态查找来对账;否则把效果报成"未知",而不是盲目重放。

证据

  • 证据等级:混合mixed)。
  • 有价值发现:NOA Action Digest 定义了从授权记录、参数承诺、执行授权、一次性 nonce 推导的尝试级关联构造,同时明确说它不定义执行绑定,相等也不证明执行或效果;SCITT AI-agent 动作收据 profile 把签发者认证的动作记录和先前授权、控制器报告结果、外部世界效果声明分开;Google AIP-151 支持稳定操作句柄供后续状态查询,Kubernetes API 约定区分期望规格和观察状态。
  • 未验证:失败关闭的适配器比对是这个模式的综合,不是被引用规范保证的;对比生产证据还没建立。安全依赖认证授权、抗碰撞域分离规范化、原子消费、比对后的不可变值、完整中介。

怎么用

  • 用在"批准后漂移会出事"的动作上:支付、消息、部署、权限变更、破坏性操作、外部可见承诺。
  • 每个能改变含义或爆炸半径的字段都进身份。只哈希工具名、把接收者或参数留在身份外,是"审批表演"。批准前解析别名,避免事后解析改变资源。
  • 用文档化的规范化 profile。拒绝重复键、不支持的数值形式、歧义路径、未知字段,而不是在服务间各按各的归一化。
  • 授权保持短时有效、最好一次性。认证它,绑定到执行面和策略版本,防止通过更宽的适配器重放。
  • 让替代执行路径也走同一个验证点。一个不经中介的 CLI、浏览器、回退 API 都能绕过本来正确的契约。
  • 刻意测突变和竞态:改一个目标、参数、执行者、操作、策略、过期时间或 nonce;再让两个 worker 消费同一个授权。证明重复执行前被拒绝。
  • 匹配尝试后,通过适当可信的状态查询、稳定句柄或独立读回验证声明的成功标准。观察无法解决结果时报告 unknown,别拿授权摘要当成功证据。

取舍

  • 好处:在正确中介的路径上检测并拒绝"提交字段漂移";让审批范围机器可检查;把强制定位在执行边界;改善批准、尝试、观察记录之间的关联。
  • 代价:要认证信封、严格规范化、原子消费、崩溃对账、每条执行路径都中介;变更请求要再走一次审批;别名和比对后未检查的转换仍能打败幼稚实现。
  • 局限:摘要是完整性身份,不是认证、授权或执行证明。被攻破的批准人、适配器、规范化器或签名密钥仍然危险。这个模式不建立"提供的字段为真""动作明智""外部效果发生"。

模式三:策略门控工具代理(Policy-Gated Tool Proxy)

问题

AI Agent 通过 MCP 这类协议调用外部工具(数据库、API、文件系统)。一旦 Agent 能访问工具服务器,它就能带任何参数调任何工具。 "Agent 决定调这个工具"和"工具执行了"之间没有任何强制层。这造成三个缺口:

  1. 没有访问控制:Agent 能调不该调的工具(比如生产库删除,而它只被授权读)。
  2. 没有审计轨迹:出事时,没有记录"哪些 Agent 用什么参数调了哪些工具"。
  3. 没有策略强制:合规规则(数据驻留、PII 处理、限流)没法在工具调用边界强制。

方案

在 Agent 和工具服务器之间放一个透明代理。 代理拦截每次工具调用,用策略引擎评估,然后放行、阻止、或转人工审批流程。每个决策都记录进追加式审计轨迹。

核心组件:

  • 代理层:坐在 Agent 和工具服务器之间,两侧说同样的协议(MCP 进、MCP 出)。Agent 不知道自己在对代理说话。
  • 策略引擎:对照声明式规则评估每次工具调用。规则可以匹配工具名、参数值、调用者身份、时间、限流、自定义谓词。
  • 决策结果:允许、拒绝、需要审批(转人工)、转换(转发前修改参数)。
  • 审计日志:每次工具调用、策略评估结果、执行结果的追加式记录。理想情况哈希链起来,防篡改。
Agent → 代理: call_tool(name, args)
代理 → 策略引擎: evaluate(tool, args, caller)

允许: 策略引擎→ALLOW → 代理转发 → 工具返回 → 记录 → Agent 拿结果
拒绝: 策略引擎→DENY(原因) → 记录 → Agent 收到策略违规错误
需要审批: 策略引擎→REQUIRE_APPROVAL → 代理请求人批准 → 批准则转发,拒绝则回绝

证据

  • 证据等级:新兴emerging),来自 SidClaw 团队。
  • 学术基础:Beurer-Kellner 等人(2025)形式化了 Agent 安全里的"提议和执行分离"。

怎么用

什么时候用:

  • 多 Agent 系统,不同 Agent 需要不同工具权限。
  • 受监管环境(金融、医疗)要求审计轨迹。
  • 生产部署里某些工具调用要人签字。
  • 任何"Agent 有 MCP 访问权"这个粒度太粗的场景。

实现方式:

  1. 独立代理进程:作为单独服务跑。Agent 连代理,代理连工具服务器。和任何 Agent 框架兼容。
  2. 中间件/包装:包工具服务器 SDK。基础设施少,但耦合特定语言或框架。
  3. 网关模式:一个代理前置多个工具服务器,跨所有工具提供统一策略和审计。

策略规则示例:

rules:
  - match: { tool: "database_query", args.query: "DELETE *" }
    action: deny
    reason: "批量删除需要人工执行"

  - match: { tool: "send_email", rate: "> 10/hour" }
    action: deny
    reason: "超过邮件限流"

  - match: { tool: "deploy_production" }
    action: require_approval
    channel: slack

取舍

  • 好处:对 Agent 和工具服务器都透明,两边都不用改代码;策略声明式、可审计,不埋在 Agent 提示词里;哈希链审计日志提供防篡改的合规记录;能在边界强制限流、数据驻留、PII 规则;和其它模式(人在回路、可观测性)可组合。
  • 代价:每次工具调用加延迟(策略评估 + 日志);策略规则要随工具和需求演进维护;人工审批流程引入阻塞等待和可用性依赖;抓不住跨多次工具调用的策略违规(那需要更上层的编排);代理要和它前置的工具服务器一样可用。

模式四:沙箱授权(Sandboxed Tool Authorization)

问题

工具授权既要灵活又要安全。静态白名单在多个维度上撑不住

  • 多环境:开发(宽松)vs 生产(严格)。
  • 不同 Agent 角色:编码 Agent 需要文件系统访问,消息 Agent 不该有。
  • 层级委派:子 Agent 应该继承父级限制,但加更多约束。
  • 插件生态:外部工具要动态纳入,不能手动更新白名单。

Agent 需要一个支持模式匹配、默认拒绝语义、层级继承的策略系统。

方案

基于模式匹配的策略 + 默认拒绝 + 继承。 工具通过匹配编译后的模式(精确、正则、通配)授权,拒绝列表优先于允许列表。子 Agent 继承父策略并加额外限制,基于 profile 的层级给常见 Agent 类型提供预设。

这和学术上的 Action Selector 模式(Beurer-Kellner 等人,2025)一致:把 LLM 当指令解码器而不是活体控制器,执行前用严格 schema 验证工具参数,防止工具输出不经额外验证就重新进入选择器提示词。

核心概念:

  • 模式匹配:精确匹配(exec)、通配(fs:*)、正则式(*test*)。
  • 默认拒绝:空允许列表拒绝所有工具;显式允许列表只允许匹配的工具。
  • 拒绝优先:先算拒绝列表,匹配拒绝模式的工具不管允许列表都拦。
  • 相关工具继承:某些工具隐式授予相关权限(比如 exec 允许 apply_patch)。
  • 层级策略继承:子 Agent 策略继承父级,加额外拒绝规则。
  • Profile 分层:预定义 profile(minimalcodingmessagingfull)快速配置。
function makeToolPolicyMatcher(policy: ToolPolicy) {
  const deny = compilePatterns(policy.deny);
  const allow = compilePatterns(policy.allow);
  return (name: string) => {
    const normalized = normalizeToolName(name);
    // 拒绝优先
    if (matchesAny(normalized, deny)) return false;
    // 空允许 = 全放行(默认拒绝由调用方处理)
    if (allow.length === 0) return true;
    // 需要显式允许
    if (matchesAny(normalized, allow)) return true;
    // 相关工具继承
    if (normalized === "apply_patch" && matchesAny("exec", allow)) return true;
    return false;
  };
}

Profile 预设:

const TOOL_PROFILES = {
  minimal:   { allow: ["session_status"] },                    // 最少
  coding:    { allow: ["group:fs", "group:runtime", "group:sessions", "group:memory", "image"] },
  messaging: { allow: ["group:messaging", "sessions_list", "sessions_history", "sessions_send", "session_status"] },
  full:      {},                                              // 空策略 = 全放行
};

子 Agent 策略继承:

const DEFAULT_SUBAGENT_TOOL_DENY = [
  // 会话管理,主 Agent 管
  "sessions_list", "sessions_history", "sessions_send", "sessions_spawn",
  // 系统管理,子 Agent 调太危险
  "gateway", "agents_list",
  // 状态/调度,主 Agent 协调
  "session_status", "cron",
];

function resolveSubagentToolPolicy(config) {
  const deny = [...DEFAULT_SUBAGENT_TOOL_DENY, ...(config?.deny ?? [])];
  return { allow: config?.allow, deny };
}

证据

  • 证据等级:生产验证validated-in-production),来自 Clawdbot。
  • 学术基础:Beurer-Kellner 等人的 Action Selector 模式。

怎么用

  1. 定义工具组:把相关工具分组(group:fsgroup:runtime),批量写策略。
  2. 选 profile:拿预定义 profile(minimalcodingmessagingfull)当基线。
  3. 加显式规则:在 profile 上叠加 allow/deny 规则。
  4. 配子 Agent 限制:给派生的 Agent 定义额外 deny 规则。
  5. 运行时过滤:用策略匹配器在把工具交给 Agent 前先过滤一遍。

要避开的坑:

  • 过宽的模式* 这种通配可能意外授予过多权限,偏好具体模式。
  • 漏了拒绝优先:总是先算 deny 再算 allow,否则 allow 规则能绕过安全意图。
  • 忘了相关工具:允许了 exec,记得 apply_patch 也该放(它是文件操作)。
  • 继承混淆:子 Agent 策略是在父策略之上加限制,不是整个替换。

取舍

  • 好处:模式灵活,通配和分组让大工具集策略写得很简洁;默认拒绝,防止意外授权;层级控制,子 Agent 不用改父策略就能进一步限制;profile 预设,常见 Agent 类型开箱即用;插件支持,工具组能通过动态发现纳入插件工具。
  • 代价:模式复杂,正则式可能让人困惑,语法错误可能授予意外访问;策略爆炸,很多 Agent 各自策略难以管理和审计;求值顺序必须一致(deny 先于 allow),bug 会引发安全问题;"哪些工具相关"(比如 execapply_patch)是主观判断,可能覆盖不全。

四个模式怎么选

场景 推荐模式
高风险动作要人点头 人在回路审批
审批后可能被篡改,要精确绑定 精确动作授权绑定
多 Agent / 受监管环境,要统一门禁 + 审计 策略门控工具代理
要灵活的策略 + 默认拒绝 + 层级继承 沙箱授权

四个模式是权限与审批的四件套人在回路解决"谁点头",精确绑定解决"批的和干的必须是一个",策略代理解决"统一门禁 + 审计",沙箱授权解决"策略怎么写得灵活又安全"。通常组合用:沙箱授权定策略,策略代理做强制,人在回路补高风险,精确绑定堵篡改。

实践清单

  • 高风险函数(DB 删改、生产部署、破坏性操作)插人工审批门,默认拒绝超时
  • 审批请求带足上下文:为什么、影响什么、可逆吗、有什么替代
  • 审批要绑定到完整动作(目标/参数/执行者/策略/过期/nonce),执行前重新推导比对
  • 批准后动作字段变了就拒绝,原子消费防重放,比对后转换要重新授权
  • Agent 和工具之间插策略代理:允许/拒绝/需审批/转换四种结果,全记录
  • 策略代理审计日志哈希链防篡改,跨所有工具统一策略
  • 工具授权默认拒绝,deny 优先于 allow,用模式(精确/通配/正则)和分组写策略
  • 子 Agent 继承父策略 + 额外 deny,别整个替换
  • 让所有替代执行路径(CLI、浏览器、回退 API)也走同一个验证点

本章小结

  • 人在回路审批:高风险函数插审批门,Slack/邮件/SMS 多渠道,审计轨迹完整。
  • 精确动作授权绑定:批准绑定到完整动作的确定性身份,执行前重新比对,TOCTOU 缺口关闭。
  • 策略门控工具代理:透明代理 + 策略引擎 + 哈希链审计,统一强制和记录。
  • 沙箱授权:模式匹配 + 默认拒绝 + 层级继承 + profile 预设,策略灵活又安全。
  • 权限与审批把"Agent 能干什么"从拍脑袋变成可配置、可审计、可强制。

下一章讲凭据与出口:Agent 手里的钥匙和门。本地优先凭据代理、非托管支出控制、出口封锁、零知识出口。

📑 Agent 模式实战:生产级 AI Agent 的工程模式

1 第 1 章 什么是 Agent 模式 2 第 2 章 规划-执行-观察:先想清楚,再动手 3 第 3 章 反思闭环:让 Agent 学会检查自己的作业 4 第 4 章 委派:让主 Agent 学会把活分出去 5 第 5 章 上下文预算治理:把 token 当成钱来管 6 第 6 章 上下文压缩与精选:装不下怎么办 7 第 7 章 上下文最小化:别让脏东西留在脑子里 8 第 8 章 记忆体系:让 Agent 记得住过去 9 第 9 章 学习沉淀:让 Agent 和团队一起变聪明 10 第 10 章 工具接口哲学:让 Agent 能用、好用、用得起 11 第 11 章 工具发现:让 Agent 在几百个工具里找到对的 12 第 12 章 执行环境:Agent 在哪动手、怎么动手 13 第 13 章 代码执行与沙箱:先写码,再跑码 14 第 14 章 结构化输出与契约:让 Agent 的输出能接住 15 第 15 章 验证循环:Agent 怎么检查自己的作业 16 第 16 章 评测基建:怎么系统地检验 Agent 17 第 17 章 可观测性:看见 Agent 在想什么、在干嘛 18 第 18 章 韧性工程:扛得住部分失效 19 第 19 章 威胁模型:先看风险长什么样,再谈防御 20 第 20 章 控制流隔离:把"谁做决定"和"谁执行"分开 21 第 21 章 权限与审批:谁有权干什么、谁点头 22 第 22 章 凭据与出口:Agent 手里的钥匙和门 23 第 23 章 多智能体信任:多个 Agent 之间怎么互信、怎么审计 24 第 24 章 反馈信号设计:给 Agent 的是信号,不是更大的提示词 25 第 25 章 评测驱动的改进:让 Agent 在真实使用和对抗测试里变强 26 第 26 章 强化学习:把反馈变成训练信号 27 第 27 章 复合式进化:让 Agent 系统越用越值钱 28 第 28 章 多智能体协调:让一群 Agent 一起干活不掉链子 29 第 29 章 模型路由:谁用哪个模型,怎么用得起 30 第 30 章 推理搜索结构:让 Agent 多想想,而不是一条道走到黑 31 第 31 章 控制谱系:从自动补全到完全自主的滑动条 32 第 32 章 团队与产品:把 Agent 变成团队资产,而不是个人玩具
← 返回本书大纲