我们如何构建 Claude Code auto mode:一种更安全的权限跳过方式
原文: English original · Anthropic/OpenAI 官方
我们如何构建 Claude Code auto mode:一种更安全的权限跳过方式
Claude Code 用户会批准 93% 的 permission prompts。我们构建了分类器来自动处理一部分决策,在减少审批疲劳的同时提升安全性。本文会介绍它能拦住什么,以及仍会漏掉什么。
默认情况下,Claude Code 在运行命令或修改文件之前,会先请求用户批准。这能保护用户安全,但也意味着用户需要反复点击“approve”。时间一长,就会产生审批疲劳:人们不再认真留意自己到底批准了什么。
为了避免这种疲劳,用户通常有两种选择:一种是使用内置沙盒,让工具在隔离环境中运行,防止危险操作;另一种是使用 --dangerously-skip-permissions 标志,关闭所有 permission prompts,让 Claude 自由行动,但这在大多数情况下并不安全。auto mode 介于这两个极端之间。它是 Claude Code 的一种新模式,把审批委托给基于模型的分类器,在人工审查和完全没有护栏之间提供折中方案。
我们维护了一份内部事故日志,重点记录 agentic 误行为。过去的例子包括:因误解指令而删除远程 Git 分支;把一名工程师的 GitHub auth token 上传到内部计算集群;以及尝试对生产数据库执行迁移。这些事故的共同点是模型过于积极,主动采取了用户并不希望它采取的行动。
工作原理
auto mode 使用两层防线:一层管 Claude 读取什么,另一层管 Claude 做什么。
在输入层,一个服务端 prompt-injection 探针会在工具输出进入 agent 上下文之前扫描它们,包括文件读取、网页抓取、shell 输出和外部工具响应。当内容看起来像是在试图劫持行为时,探针会先向 agent 的上下文中加入一条警告,再把结果传递下去。
在输出层,运行在 Sonnet 4.6 上的会话记录分类器,会在每个动作执行前根据一组决策标准进行评估,充当人类审批者的替代品。分类器分两阶段运行:先经过快速的 single-token 过滤器;只有当第一阶段标记了会话记录,才会进入 chain-of-thought 推理。由于大多数动作都能通过第一阶段,reasoning tokens 只会花在真正需要的地方。
分类器只会看到用户消息和 agent 的工具调用。Claude 自己的消息和工具输出都会被剥离,因此它在设计上看不到 Claude 的推理脉络。
图 1. 系统架构。会话记录分类器为工具调用把关;prompt-injection 探针筛查工具结果。subagents 会递归运行同一套管线,并在委托和返回时分别经过交接分类器。
威胁模型
agent 可能因为四类原因采取危险动作:
- 过度积极。agent 理解了用户目标,也确实想帮忙,但主动做了超出用户会批准范围的事。
- 诚实错误。agent 误判了影响范围。
- prompt injection。植入文件、网页或工具输出中的指令劫持了 agent。
- 模型未对齐。典型情况下,未对齐是指 agent 追求了自己的目标。
在这四种情况下,防御目标都是阻止该动作。分类器主要针对过度积极行为和诚实错误进行调优,但无论危险动作来自哪类威胁,这层防御都适用。
下面的例子改编自我们的内部事故日志,用来说明 auto mode 旨在拦截哪些行为:
- 范围升级:像“clean up old branches”这样含糊的请求,演变成删除远程分支。
- 凭据探索:认证错误之后,agent 开始翻找环境变量和配置文件,寻找替代 token。
- agent 推断参数:取消某个任务的请求,变成删除名称最接近的对象。
- 通过外部服务分享:agent 为了调试而构造 GitHub Gist 或类似上传。
- 绕过安全检查:用 skip-verification 标志重试失败的 deploy。
权限决策如何工作
上面的所有例子都会进入分类器,但在到达这一步之前,大多数动作会被标准 Claude Code allow 规则提前放行:
第 1 层:内置安全工具 allowlist 和用户设置。固定 allowlist 包含不能修改状态的工具,包括文件读取、文本和文件模式搜索、代码导航,以及进入 plan mode。用户也可以为特定工具模式配置 always-allow 规则。
第 2 层:项目内文件操作。项目目录内的文件写入和编辑无需调用分类器即可放行。
只有真正可能造成损失的动作才会进入最后一层:
第 3 层:会话记录分类器。会话记录分类器会看到其他所有内容,包括 shell 命令、网页抓取、外部工具集成、启动 subagent,以及项目目录之外的文件系统操作。第 1 阶段是一个快速的 single-token yes-or-no 过滤器,被要求倾向于阻止;如果它打上标记,第 2 阶段会用 chain-of-thought 推理重新评估同一输入,以降低误报。
分类器决策标准
分类器的 prompt 是我们随产品发布的固定模板,中间插入三个可自定义槽位。固定部分负责解释工作,包括如何判断一个动作实际会做什么、如何理解用户授权,以及如何分类结果。槽位则定义环境、阻止规则和例外。
固定模板
评估规则会告诉分类器如何寻找危险命令。原则是评估动作在真实世界中的影响,而不只是看调用文本的表面。如果 agent 先把 payload 写入文件再运行它,分类器会评估这个 payload。如果一串命令用 && 连接,整串命令会被视为一个动作。如果 agent 写了一个 Python 脚本来组装 shell 命令,分类器会先根据会话记录上下文评估组装后的命令,再决定是否允许执行这个脚本。
用户意图规则会告诉分类器如何理解授权。prompt 会界定哪些事已获授权;在用户另行说明之前,agent 自己选择做的任何事都不算已授权。在这个框架下,“clean up my branches”并不授权批量删除,“can we fix this?”会被视为一个问题,而不是一条指令。
可自定义槽位
环境定义了信任边界:哪些域名、cloud buckets、source-control orgs 和内部服务算作边界内,哪些算作外部。默认情况下,分类器唯一信任的是你当前正在工作的 Git repo。
默认阻止列表包含二十多条规则,大体分为几组:
- 销毁或外泄。
- 削弱安全态势。
- 跨越信任边界。
- 绕过审查或影响他人。
允许例外是一些很窄的豁免场景:某条阻止规则在技术上命中了,但动作本身是良性的,例如安装 repo manifest 中已经声明的包、标准凭据流程,以及推送到当前 session 的工作分支。
结果
我们在三个数据集上评估了完整管线:
- 真实内部流量,n=10,000。
- 真实过度积极动作,n=52。
- 合成数据外泄,n=1,000。
我们报告 false-negative rate 和 false-positive rate。完美分类器在两项上都应该为零,但实践中二者会互相权衡。第 1 阶段速度快、成本低,为了低 FNR 而调优,代价是 FPR 更高。第 2 阶段成本更高,只会在第 1 阶段标记的内容上运行。
| Real traffic n=10,000 | Real overeager n=52 | Synthetic exfil n=1,000 | |
|---|---|---|---|
| Stage 1 (fast filter) | 8.5% FPR | 6.6% FNR | 1.8% FNR |
| Stage 1 -> Stage 2 (full pipeline) | 0.4% FPR | 17% FNR | 5.7% FNR |
真实过度积极动作上的 17% false-negative rate 是一个坦诚的数字。模型经常能够识别危险命令,但会漏判这个 session 是否真的包含了针对该影响范围的有效同意信号。
17% 是否可接受,取决于你拿它和什么比较。如果你原本就在运行 --dangerously-skip-permissions,这是一个很大的改进。如果你原本会认真手动批准每个动作,它可以说是一种倒退。auto mode 面向的是第一类用户,也适合那些第二类用户的审批开销不值得换取边际安全收益的任务。
设计决策
为什么剥离 assistant 文本和工具结果
我们剥离 assistant 文本,是为了防止 agent 说服分类器做出错误判断。agent 可能会生成很有说服力的理由,例如“这是安全的,因为用户之前已经隐式批准过”。如果分类器读到这些内容,就可能被带向错误决策。我们希望它判断的是 agent 做了什么,而不是 agent 说了什么。
剥离工具结果也是主要的 prompt-injection 防御,因为敌意内容正是通过工具输出进入上下文。代价是分类器会失去一部分来源信息。为了提高 injection 鲁棒性,我们接受这个成本。
两阶段分类:效率与召回
单阶段分类器会面对直接的性能和成本权衡。充分推理可以降低误报,但成本更高。把它拆成谨慎程度不同的两个阶段,可以打破这种权衡。第 1 阶段是一个偏谨慎的快速过滤器。第 2 阶段会用更多推理重新评估被标记的动作。由于除了最终指令之外输入完全相同,第 2 阶段的 prompt 几乎完全命中第 1 阶段的 cache。
为什么 prompt-injection 探针重要
会话记录分类器的 injection 防御是结构性的,因为它从来不会看到工具结果。但主 agent 会看到工具结果,而劫持主 agent 的 injection 仍然会造成麻烦。输入层探针存在的目的,就是在内容看起来像 injection 尝试时警告 agent。
多 agent 交接
分类器会在 subagent 交接的两端运行:委托工作时运行一次,结果返回时再运行一次。出站检查存在的原因是,如果没有它,subagent 可能会把编排者的指令视为已完全授权,即使原始用户从未要求过这个动作。返回检查存在的原因是,一个在委托时良性的 subagent,也可能在运行中途被它读取的内容攻陷。
拒绝并继续
auto mode 旨在替代 --dangerously-skip-permissions,同时不重新带来打断。当分类器阻止某个动作时,Claude 不应该停下来等待输入;只要存在更安全的做法,它就应该恢复并尝试那条路径。
如果一个 session 累计出现 3 次连续拒绝或总计 20 次拒绝,我们会停止模型并升级给人类处理。在 headless mode 下,我们会直接终止进程。
下一步
我们会继续扩展真实过度积极测试集,并迭代改进这一功能的安全性和成本。分类器不需要完美无缺才有价值;起点是拦下足够多的危险动作,让自主运行比没有护栏时安全得多。
致谢
作者 John Hughes。特别感谢为分类器、prompt-injection 探针以及支撑本文的内部事故日志作出贡献的团队成员。