07:Permission & Safety:Claude Code 如何裁决一次动作能不能执行
上一篇讲 plan,重点是 Claude Code 怎样先把任务整理成一条行动路线。
07:Permission & Safety:Claude Code 如何裁决一次动作能不能执行
上一篇讲 plan,重点是 Claude Code 怎样先把任务整理成一条行动路线。
但 plan 只是“准备怎么做”。真正动手时,系统还要继续问:
这一步现在能不能执行?
这个问题很具体。比如 plan 里写“读取配置确认服务启动参数”,听起来很正常。可真正落到工具调用时,目标文件可能是 .env。再比如 plan 里写“运行测试验证修改”,如果命令是 pnpm test,通常是合理的验证动作;如果命令变成下载脚本并直接执行,风险就变了。
所以 07 先收住这一层:
Claude Code 面对一个准备执行的动作时,怎样把它裁决成可以做、先问人,或者换一条路。

这篇文章要拆的主线:plan 里的动作怎样被裁决
06 里说,plan 像一份行动草稿:先看什么、改什么、怎么验证。
07 接着看草稿里的动作怎样落地。Claude Code 会先把 plan 里的步骤落到具体工具调用上。每一步真正碰到文件、命令、网络或外部系统之前,都要经过一次运行时裁决。
可以把这个过程想成一条很短的链:
计划里的动作
-> 具体工具调用
-> 工具参数
-> 权限裁决
-> 执行、询问或改计划
这条链里最容易被忽略的是“具体工具参数”。
“读文件”只是动作类型;读哪个文件,才决定风险。“运行命令”只是动作类型;命令会不会删除、联网、改 git 历史,才决定是否要停下来。
几篇源码分析和安全文章给我的启发也是这一点:权限系统的核心是工具真正执行前,运行时怎样把工具、参数、上下文和规则放到一起判断[1],[2]。
为什么需要 permission:动作落到参数上以后,风险才清楚
Claude Code 的能力来自它能真正动手。
它能读项目、搜代码、改文件、跑命令,也能接 MCP、浏览器或其他外部工具。能力越接近真实环境,越需要边界。
很多风险会在“具体怎么做”的参数里变清楚。
比如:
-
读配置。
读config.example.ts可能只是理解项目结构;读.env可能把密钥带进上下文。 -
清理构建产物。
删除dist/可能合理;路径拼错或通配符过大,就可能误伤源码。 -
运行测试。
pnpm test可能只是本地验证;某些测试脚本可能会访问网络、数据库或本地服务。 -
调用外部系统。
读 issue 是观察;改 PR、触发部署、写数据库就是副作用。
所以 permission 解决的是动作意图和真实执行之间的落差。
prompt、CLAUDE.md、项目规则能提醒模型“应该怎么做”。运行时权限负责在工具执行前把动作收住:这次动作目标是什么,是否在任务范围内,风险能不能接受。
这也是为什么 03 讲 prompt 时要把“软规则”和“硬边界”分开。规则写给模型看,权限裁决发生在工具执行前。
一次动作进入权限判断前,Claude Code 会看什么
如果只按工具名判断,很快就会失真。
Read 看起来安全,但读密钥不安全。Bash 看起来危险,但跑测试、查版本、列目录又是 coding agent 的常规动作。Edit 有副作用,但修 bug 时它又是必要能力。
更合理的判断方式,是把一个动作拆成三层:
| 层次 | 关注点 | 例子 |
|---|---|---|
| 工具类型 | 这是观察、写入、命令,还是外部调用 | Read、Edit、Bash、MCP |
| 目标参数 | 它具体指向哪里 | 文件路径、命令文本、URL、服务名 |
| 当前理由 | 它为什么服务于这次任务 | 是否在 plan 范围内,是否用于验证 |
这样看,权限判断会从“Bash 危险,所以都问”,变成更具体的问题:
这次 Bash 要跑什么?为什么要跑?它会影响哪里?有没有更低风险的做法?
这也让 plan 变得有用。plan 里写清了范围、边界和验证方式,权限裁决就能拿它当理由。动作和 plan 对得上,风险会更容易解释;动作开始越界,系统就有理由停下来。
allow、ask、deny:一次动作的三个出口
一次动作经过权限裁决后,大致有三个出口。
| 出口 | 含义 | 下一步 |
|---|---|---|
| allow | 风险低,或规则已经允许 | 直接执行工具 |
| ask | 动作可能合理,但需要用户看一眼 | 暂停,说明风险,等待确认 |
| deny | 命中明确边界 | 不执行,换路径或改计划 |
这里真正要抓住的是:三个出口都应该带理由。
allow 最好知道为什么能放行。ask 要告诉用户风险在哪里。deny 也应该给出可继续的方向,让下一轮还有路可走。
从源码分析和安全文章的角度看,成熟的权限链会把裁决放到工具执行层附近,连同参数检查、hook、日志和错误回流一起处理[1],[2]。这样做的价值,是让每次裁决都能被解释,也能被后续环节接住。
比如:
action: read
target: .env
decision: deny
reason: 目标像敏感配置
fallback: 读取 .env.example,或请用户提供脱敏字段
这个结果比直接拒绝更有用。它告诉 agent 下一轮可以怎么继续。
permission mode:决定默认怎么问
permission mode 主要影响协作节奏。
有些任务需要用户一步步审:项目敏感、代码库陌生、动作影响范围不清楚。
有些任务可以少问一点:用户已经信任方向,仓库在隔离环境里,目标也很明确。
还有一种情况是先规划:只读、搜索、理解代码,暂时不修改文件。
我会把 permission mode 理解成“默认怎么问人”的策略。
它可以让 Claude Code 更谨慎,也可以让它更连续。但动作的真实风险仍然来自工具、参数和环境。一个会删除文件的命令,即使放在更宽松的模式里,仍然需要被当成高风险动作处理。
所以更稳的设计通常是组合式的:
- mode 决定默认审批节奏。
- permission rules 处理具体动作和参数。
- sandbox 限制命令执行后的影响范围。
- logs/session trace 记录发生过什么。
Fluid Attacks 把这些放在一起看:预过滤工具、规则评估、权限模式、hooks、sandbox 和 session logs 共同构成防线[2]。这个视角比单独讨论某个模式更接近真实工程系统。
被拒绝以后怎么办:权限结果也要回到 Agent Loop
权限拒绝更像任务路线上出现了一个路障。
它更像工具结果的一种:告诉 agent “这条路现在走不通”。接下来要做的,是把这个结果放回 Agent Loop。
假设 Claude Code 想读 .env,被拒绝了。下一步可以有几种走法:
- 读
.env.example、README 或配置 schema。 - 让用户提供脱敏后的关键字段。
- 根据错误日志和示例配置继续推断。
- 修改 plan,把“读取真实环境变量”改成“使用公开配置和日志定位问题”。
- 如果确实必须访问敏感信息,停下来讲清理由,请用户决定。
命令被拒绝也一样。
rm -rf dist 被拒绝后,可以先列出目录、说明清理目标,再请求确认;curl | sh 被拒绝后,可以改成阅读安装说明、固定下载来源,或者让用户手动执行。
这部分把 permission 和 Agent Loop 连起来了:
权限裁决会给下一轮判断增加一个新的事实。
一个好的 permission result,最好带三样东西:
- 被裁决的动作。
- 裁决结果和原因。
- 可继续的替代路径。
这样 agent 可以避开原来的危险路径,沿着更合适的证据继续推进。
sandbox 和 permission 的边界
sandbox 和 permission 很容易混在一起。
我会这样分:
permission 决定这次动作能不能执行;sandbox 限制动作执行以后最多影响哪里。
比如 pnpm test 被允许执行以后,sandbox 可以继续限制它能读写哪些目录、能不能访问网络、能不能碰到宿主环境。这样用户可以减少低风险命令的反复确认,系统也能把命令放在更清楚的运行边界里。
Fluid Attacks 的安全分析把 sandbox 放在一条纵深防线里看:它和预过滤工具、规则评估、session logs 一起降低真实执行的影响面[2]。Backslash 这类安全团队视角的文章则提醒,隔离环境也有边界:agent 能读到的敏感信息、能访问的网络、能写的目录,仍然会影响风险[5]。
所以 sandbox 适合回答“放行以后影响面多大”。
permission 适合回答“这次是否应该放行”。
两者放在一起,才比较稳。
hooks 在权限链里的位置
hooks 在 07 里只讲位置,配置细节留到后面。
它可以在工具执行前后补检查、补记录、补自动化。
比如:
- Bash 执行前检查命令里有没有危险片段。
- Edit 之后自动跑格式化。
- 工具调用后记录审计事件。
- 任务停止前做通知或收尾。
在权限链里,hooks 像一个可插拔的拦截点。默认权限规则负责基础裁决,hooks 可以接入团队自己的流程。
但 hooks 本身属于扩展机制。第 10 篇会单独讲它的生命周期、事件类型和配置方式。07 只需要留一句位置说明:
hooks 可以补强动作裁决,但主线仍然是“动作能不能执行”。
一个例子:读 .env 和运行测试
拿两个动作对比,permission 的作用会更清楚。
第一个动作是读 .env。
Claude 可能是为了排查启动失败,想确认环境变量。这个意图合理,但目标文件敏感。一次比较稳的裁决会把它拦下来,然后给下一步:
- 先读
.env.example。 - 再看配置读取逻辑。
- 如需真实值,请用户提供脱敏摘要。
第二个动作是运行 pnpm test。
用户要求修 bug,plan 里也写了要验证。此时测试命令更像合理动作。系统仍然可以看命令参数、工作目录和网络行为,但它的理由更充足:这个动作服务于验证。
这两个例子真正要比较的是:
- 目标是什么。
- 为什么需要它。
- 会不会碰敏感信息或产生副作用。
- 被拒绝以后有没有替代路径。
这就是动作裁决的核心。
对我写 Agent 的启发:设计 PermissionDecision
如果自己写一个 coding agent,我会把权限判断设计成结构化对象。
它最好不只返回“允许 / 拒绝”,还要告诉用户和 agent:为什么这么判,接下来怎么走。
一个最小的 PermissionDecision 可以包含这些字段:
| 字段 | 作用 |
|---|---|
| action | 这次准备做什么 |
| tool | 使用哪个工具 |
| target | 路径、命令、URL 或外部资源 |
| risk | 风险等级 |
| reason | 为什么这个动作服务于当前 plan |
| decision | allow、ask、deny |
| evidence | 命中了哪些规则或上下文判断 |
| fallback | 被拒绝以后可以怎么继续 |
这里最值得保留的是 fallback。
deny 如果只返回“不能做”,agent 很容易卡住。带上 fallback,下一轮就能继续推进:
decision: deny
reason: 目标路径匹配敏感配置
fallback: 读取 .env.example,或请求用户提供脱敏字段
ask 也要讲清楚风险。用户最好看到这条命令准备做什么、影响哪里、为什么需要它,而不只是看到“是否允许运行命令”。
这样 permission 既是防线,也是一种协作界面。
我现在对 Permission & Safety 的理解可以收成一句话:
权限系统把模型的动作意图,翻译成可解释、可拦截、可继续推进的运行时决策。
参考资料
Claude Code 源码分析与安全解读
-
[1] Claude Code 源码架构分析(含可以启动的源码本地部署)- GitCode
本文主要参考它关于toolExecution.ts如何串起 schema 校验、权限判断、hooks、telemetry 和错误处理的分析。 -
[2] Claude Code engineering - Fluid Attacks
本文主要参考它关于预过滤工具、规则评估、权限模式、hooks、sandbox 和 session logs 这些纵深防御的分析。 -
[3] Claude Code Architecture (Reverse Engineered) - Substack
本文主要参考它关于 primitive tools、静态分析层和多级白名单的反向工程视角。 -
[4] Claude Code 源代码泄露?万字解析深入其 Agent 编排系统的架构与实现 - GitCode
本文主要参考它关于 utils、tools、hooks、services 等模块中权限、bash 安全和消息处理职责分布的分析。 -
[5] Claude Code Source Leaked: Implications for Security Teams - Backslash Security
本文主要参考它从企业安全团队视角对工具权限、供应链风险、context poisoning 和 permission fatigue 的分析。
下一篇继续追的问题
这一篇先把 Permission & Safety 理解成动作裁决层。
核心结论是:
plan 说明准备怎么做,permission 判断这一步能不能做。
到这里,Claude Code 已经能看见上下文、整理 plan、裁决动作,再通过工具接触真实项目。
但这些动作发生以后,还要留下痕迹。否则用户很难知道它为什么放行、为什么拒绝、跑了什么命令、哪里失败、哪些步骤最耗时、上下文什么时候开始膨胀。
所以 08 继续追:
Claude Code 如何记录工具调用、权限结果、错误、成本和执行轨迹,让 Agent 的运行过程可调试、可复盘、可优化?