
图 1:Approval 不是一个脱离上下文的按钮。批准分支仍要经过可信执行器复检,拒绝分支必须以零外部副作用停止。封面动画仅用于表现状态流动。
1. 先给动作分级,再谈审批
最容易想到的安全策略是“凡是工具调用都问人”。它看似保守,实际会制造 Approval Fatigue:Reviewer 反复确认低风险动作,最后习惯性点击允许,真正危险的请求反而混在噪声里。
更可用的起点是按最坏可信后果给工具分级:

图 2:分级由 Tool Policy Registry 和业务策略定义,不由 Agent 自报。“模型认为风险低”不能成为授予执行权的依据。
这里有三个经常被忽略的细节。
1.1 只读不等于无风险
lookup_ticket 不会修改数据,但仍可能跨租户读取工单、一次导出过多记录,或把结果带入不该出现的 Context。只读动作可以少一道人工审批,不能少数据范围和审计。
1.2 可逆必须有真正的回滚契约
“理论上能改回来”不算可逆。系统至少要知道:
本篇 Lab 对 update_ticket_label 要求提供同一张工单的 rollback;缺失或指向其他资源都会被拒绝。
1.3 风险属于能力,不属于一句 Prompt
工具的风险等级、允许环境、所需角色和凭据范围应进入版本化注册表。模型只负责提出动作,不应同时充当申请人、风险判定者和授权者。
2. 三层控制,分别回答三个问题
安全边界经常被压缩成一个 allow / deny 开关,随后出现两个误区:开了 Sandbox 就不需要审批,或者有人批准就可以放开系统权限。两者都不成立。

图 3:任何一层拒绝,动作都必须停下;任何一层给出的“允许”,都不能替其他层授权。Prompt 和 Guardrail 可以帮助识别问题,但不是可执行权限边界。
2.1 Sandbox:运行时技术上能碰到什么
Sandbox 限制文件可写目录、网络目标、进程、挂载和系统资源。Codex 官方把 Sandbox 与 Approval 分开描述:前者约束技术能力,后者决定何时需要用户许可;默认配置会限制工作目录外写入和网络访问。
Claude Code 的官方文档也明确区分 permissions 与 OS 级 sandbox。仅在权限规则里禁止文件读取,并不必然阻止另一个 shell 工具绕路访问;真正的文件与网络隔离要由 Sandbox 承担。
2.2 Policy + Approval:这一份动作是否获准
Policy Registry 先根据工具和业务环境决定:
需要审批时,系统不执行工具,而是把 ActionProposal 与 Approval Envelope 一起持久化。Reviewer 看到的预览必须来自这份 Proposal,并确认其指纹仍与 Envelope 一致,而不是让模型另写一段摘要代替原参数。
2.3 IAM + Trusted Executor:后端最终允许什么
Agent 运行时不应长期持有生产 Token。审批通过后,由可信执行器重新校验策略,再从 Credential Broker 获取短时、最小 scope 的凭据。外部系统仍要根据身份、资源和环境独立拒绝越权请求。
这也解释了为什么 Guardrail 不能替代授权。Guardrail 可以检查文本是否包含敏感信息、参数是否满足格式,但“检查通过”不等于调用者获得了生产权限。
3. Approval Envelope:人究竟批准了什么
一个够用的 Approval Envelope 至少要绑定这些字段:
Lab 用规范化 JSON 计算动作指纹:
如果正文、收件人、工单号、环境或工具发生变化,恢复阶段计算出的指纹就不同,状态进入 blocked / action_changed。系统应重新生成 Proposal 和 Approval,而不是沿用旧批准。
这一步在防一种典型的时间差问题:检查时是动作 A,使用批准时却变成动作 B。 哈希并不能解决所有攻击,但能让“批准对象不可变”成为可测试契约。
生产实现还要多做一步
Lab 把待审批动作完整保存在受信任的 Checkpoint 中,公开报告只输出哈希和脱敏字段。真实系统应对 Checkpoint 加密、限制访问并设置保留期;分布式审批接口还应显式传递
run_id、approval_id和版本号,避免把决定恢复到错误运行。
4. Pause / Resume 是状态机,不是等待一个布尔值
安全的审批路径可以压缩成八步:
其中最重要的不是第 5 步,而是第 4、6、7 步。
4.1 副作用不能发生在暂停之前
如果节点先发消息再调用 interrupt(),人工看到的不是待批准动作,而是一份已经发生的通知。LangGraph 官方特别提醒:节点恢复时会从节点开头重新执行,放在 interrupt 前的副作用必须是幂等的,或移到单独节点。
4.2 Reviewer 不能等于 Requester
同一身份既申请又批准,会把双人控制退化成装饰。关键操作还应校验角色和身份新鲜度,例如只有 release_manager 能批准生产发布,并要求最近 5 分钟内完成过强身份验证。
4.3 拒绝和过期不能自动换一种说法重试
Reviewer 拒绝了发送动作,Agent 不应通过改写正文、换工具或等待一会儿自动重试。正确处理是终止当前 Proposal;如果业务仍需要继续,由新的证据产生新的 Proposal 和新的审批记录。
4.4 Resume 必须幂等
网络超时可能让同一条“批准”被提交两次。执行器应以稳定 action_id + fingerprint 查询回执:第一次产生副作用,后续恢复只重放 receipt。Lab 的重复恢复案例执行两次,外部 mutation count 仍为 1。
5. 跑一遍 Security Lab
实验代码位于 ai-agent-learn 的 agent-reliability-lab。它不调用模型,专门把权限契约变成可重复的确定性测试。
下载本文附件后运行:
预期结果:
这里的 100 个测试是整个 agent-reliability-lab 的回归套件,其中 15 个直接覆盖 Security 模块;policy-test 则只评估本篇的 8 个控制案例。任一结果不符合声明时,命令会返回非零退出码,并把差异写入 reports/local/security-failures.md。

图 4:PASS 只证明当前确定性夹具满足声明的审批契约。它不代表完成了渗透测试、生产 IAM 审计或模型安全评测。
5.1 八个案例分别在防什么
可以直接阅读四个入口:
-
工具策略与审批状态机 -
8 个安全案例 -
审批边界测试 -
Security Review 报告
5.2 两个十分钟故障练习
练习一:让过期批准错误地通过。
打开 datasets/security-cases.jsonl,把 approval-expired 的 decision.at 从 121 改成 119,但保留期望状态 expired。再次执行 policy-test,Release Gate 应失败。它说明测试不是只看程序能否运行,而是在比对声明的控制结果。
练习二:改变已批准参数。
在 arguments-changed-after-approval 中,把 resume_action.arguments.message 改回原文。期望仍保留 blocked,测试应失败。再把期望改为 completed / approved / 1,观察动作为什么恢复为一次执行。
完成后用 Git 恢复夹具,确认 8/8 重新通过。
Lab 没有证明什么
它没有真实 LLM、云端 IAM、Secret Manager、多租户后端或恶意输入,也没有模拟 Reviewer 账号被盗。
reviewer这样的字符串只是教学用角色。生产系统还需要身份提供商、短时凭据、资源级鉴权、速率限制、告警、渗透测试和事件响应。
6. 映射到 Codex、OpenAI Agents SDK、Claude 与 LangGraph
这些框架提供的原语不同,但工程问题相通。
OpenAI Agents SDK 的 HITL 流程会在工具需要批准时返回 interruption,并通过保存的 RunState 继续同一次运行。这解决了“如何暂停与恢复”的框架问题,不会自动替你决定“谁可以批准付款、批准绑定哪些参数”。
同理,Codex 或 Claude Code 弹出一次 permission prompt,表示当前客户端根据配置请求许可;业务系统仍然要验证后端身份和资源权限。不要把界面上的 Allow 当成生产系统的授权证明。
7. 一份可复用的 Tool Policy 模板
给新工具接入 Agent 前,可以先填写这份模板:
真正评审时,再追问七个问题:
-
这个工具最坏能造成什么后果,风险由谁维护? -
只读范围是否限制到租户、资源、字段和数量? -
可逆写入是否真的有可测试的回滚和幂等键? -
Reviewer 看到的是精确动作,还是模型生成的模糊摘要? -
参数、环境、策略或身份变化后,旧批准会不会自动失效? -
凭据是在 Agent Context 里,还是只在可信执行器中短暂出现? -
拒绝、过期、进程重启和重复恢复是否都能保持零额外副作用?
如果其中任何一项只能回答“Prompt 会提醒模型注意”,控制链还没有闭合。
收藏清单
读完本文,可以把下面这组最小实现带走:
Human Control 的判断标准不是“页面上有没有审批按钮”,而是:当人说允许时,系统能否证明执行的是他刚刚看到的那一个动作;当人说不允许时,系统能否证明没有发生外部副作用。
下一篇进入 Agent Production Loop:当权限边界已经建立,怎样继续管理生产信号、降级、Canary 与回滚,并把确认后的事故沉淀成下一版回归评估。
参考资料
-
OpenAI Codex:Agent approvals and security -
OpenAI:Sandboxing agents -
OpenAI Cookbook:Agents SDK approvals 与 RunState 恢复 -
Claude Code:Permissions -
Claude Code:Sandboxing -
LangGraph:Interrupts -
OWASP:Excessive Agency -
OWASP Top 10 for Agentic Applications 2026
订阅智宅客
AI / 技术 / 数字生活方式,新文章第一时间送到邮箱。









