> ## Content Index
> Fetch the complete content index at: https://www.hubwiz.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Bash 拒绝规则为什么拦不住：4 次 shell 变量绕过
- URL: https://www.hubwiz.com/blog/claude-code-bash-deny-rule-bypass/
- Published: 2026-10-18T17:49:16.000Z
- Updated: 2026-10-18T17:53:20.000Z
- Description: 你的规则写的是"这条命令不能跑"，检查器看到的却是"这段文字不像那条命令"。四次修复、四种 shell 行为，以及三层加固清单。
- Author: admin
- Tags: Claude Code, 权限控制, 沙箱, AI Agent 安全

**一句话结论**：配置文件里写着 `"deny": ["Bash(rm *)"]`，你读成"Agent 不能跑 rm"；工具读成"**一段文本**不匹配 `rm *` 就不执行"。这两句话之间隔着整个 shell：变量展开、词分割、命令查找、不同 shell 的方言。2026 年 10 月 3 日到 9 日，Claude Code 的 changelog 修掉了**四个**"shell 变量绕过权限检查"的案例——两次是 deny/ask 规则漏掉了命令，两次是"自动批准"把本该弹窗的命令放了过去。

**披露**：本文编译自 Gabriel Anhaia 的技术分析（dev.to，2026-10-18）。作者是 Hermes IDE 的作者，文中含产品段落，请按广告阅读。四个修复条目的原文来自 Claude Code 的 CHANGELOG；文中对 shell 行为的解释依据 bash / zsh 手册，作者明确说明他**没有**在文中实机执行那些示例，行为以手册为准。

---

## 一、四条 changelog，四种绕过形状

先把事实摆清楚。2.1.289 到 2.1.296 之间，官方 CHANGELOG 记了这四条：

| 版本      | 日期    | 修复内容（意译）                                                                        |
| ------- | ----- | ------------------------------------------------------------------------------- |
| 2.1.289 | 10-03 | **deny / ask 规则漏掉了"环境变量前缀 + 展开值"后面的命令**，例如 TZ="$HOME" rm -rf build；发生在沙箱自动放行模式下 |
| 2.1.289 | 10-03 | **裸变量赋值出现在命令之前时**，沙箱自动放行会让 deny / ask 规则被跳过                                     |
| 2.1.290 | 10-05 | **变量名在 zsh 与 bash 里含义不同的命令**被自动批准，现在改为弹窗                                        |
| 2.1.296 | 10-09 | **给 BASH\_ARGV0 赋值后再使用它的命令**被自动批准，现在改为弹窗                                        |

同一周还有"邻居"修复：2.1.290 还修了"变量经由 `declare`、`typeset`、`export`、`readonly` 前缀赋值，导致规则漏掉命令或路径"，以及"`rg` 这类只读命令在 shell 仍会把参数展开成通配符时被自动批准"。

这四条的共同点不是某个具体命令，而是**检查器必须"预测 shell 会做什么"**，而 shell 的行为比规则语言复杂得多。

## 二、为什么文本匹配必须"像 shell 一样思考"

权限文档说得很直接：Bash 规则"匹配 Claude 写出的命令文本，在 Claude Code 拆分复合命令、剥掉包装器之后"。同一页也承认：deny / ask 规则**"不是程序周围的安全边界"**，并给了例子——`Bash(rm *)` 能挡住 `rm -rf build/`，但挡不住：

```bash
/bin/rm -rf build/
bash -c 'rm -rf build/'

```

文档还写了变量场景的**预期行为**："deny / ask 规则会匹配任何前置赋值，所以 deny 里的 `Bash(rm *)` 仍然匹配 `FOO=bar rm -rf tmp/`。" 而 2.1.289 修的两个案例，正是这个承诺在**沙箱自动放行**（sandbox auto-allow）下没有兑现。

为什么这个模式最危险？因为默认情况下，**沙箱内的命令可以写当前工作目录**。沙箱对 `rm -rf build` 没有意见——它就在项目里。此时**deny 规则是唯一说"不"的东西**。它一旦被绕过，就没有第二道闸。

问题的根源是**顺序**：

```text
Claude 写出一段文本
      ↓
Claude Code 读文本、匹配规则     ← 规则在这一层
      ↓
真正的 shell 展开变量、分词、查命令  ← 行为在这一层
      ↓
命令执行

```

规则要成立，检查器就得把第二层的行为**全部预测对**，而且要针对"实际会执行这条命令的那个 shell"预测。

## 三、形状一与二：前缀赋值 vs 裸赋值

前两个修复针对两种"看起来像、行为不同"的写法。bash 手册在 SIMPLE COMMAND EXPANSION 一节给了规则：

- 有命令名时，变量赋值"被加入被执行的命令的环境，不影响当前 shell 环境"；
- **没有命令名时，赋值"影响当前 shell 环境"**。

看例子（注释是按手册推的预期结果，不是本文实机运行）：

```bash
# 前缀：X 进入 printenv 的环境
bash -c 'X=1 printenv X'        # 打印 1

# 裸赋值：X 只是 shell 变量
bash -c 'X=1; printenv X'       # 什么都不打印

# 前缀 + 展开值
bash -c 'D="$HOME" printenv D'  # 打印 home 目录

# 参数在赋值生效前就展开了
bash -c 'D="$HOME" echo "[$D]"' # 打印 []

```

最后一行最反直觉：bash 里**命令后面的词先展开，赋值文本后生效**，所以 `echo` 收到的 `$D` 还是空。`TZ="$HOME"` 这种前缀的值，只有 shell 真正执行时才知道落在哪。

裸赋值危险的原因相反：**它会留下来**。`X=1; rm -rf build` 是两条语句，第一条没有命令名，于是改变了整行的 shell 环境。changelog 没给第二个修复的具体例子，所以只能把它当成"符合这段描述的某一种形状"，不能当成 Anthropic 修的那个确切案例。文档里同类风险写得很清楚：**设置、取消或循环某些特殊变量（例如 `PATH`、`IFS`）的命令会触发弹窗。**

## 四、形状三：同一行，两个 shell

Claude Code 不是在抽象 shell 里跑命令。会话启动时它会根据你的 shell "source `~/.zshrc`、`~/.bashrc` 或 `~/.profile`"；在 macOS 上通常是 zsh——Apple 从 10.15 起把它设为新账号默认。

而 zsh 有一些变量名对 bash 毫无特殊含义。最典型的是 `path`：zsh 手册说它是"一个（冒号分隔的）命令搜索目录数组"，与 `PATH` 绑定——"小写形式是数组，大写形式是标量"。

```bash
# bash：path 只是个小写数组，PATH 不受影响
bash -c 'path=(/nowhere); command -v ls || echo no-ls'

# zsh：path 就是 PATH，ls 找不到了
zsh -c 'path=(/nowhere); command -v ls || echo no-ls'

```

按手册，bash 仍能找到一个 `ls`（可能来自 `/bin`），zsh 会打印 `no-ls`。想象一个"按 bash 推理"的检查器：它看到一个小写数组赋值加一条只读命令，找不到需要弹窗的理由。但在 zsh 下，同一段文本已经改变了 `ls` 到底指哪个程序。

文档本来就要求"写 `PATH` 要弹窗"；2.1.290 修的是**这类"名字在 zsh 里含义不同"的情况**。changelog 没列具体覆盖了哪些名字，`path` 只是 zsh 手册里写明的那个。

## 五、形状四：BASH\_ARGV0 让 `$0` 变成命令

Bash 5.0（2019-01-07 公布）加了"`BASH_ARGV0`：展开为 `$0`，赋值时设置 `$0`"这条行为。手册的表述一致："给 `BASH_ARGV0` 赋值会把 `$0` 设成同一个值。"

于是**一行命令可以在命令位置完全不写程序名**：

```bash
# 需要 bash 5.0+，用 bash --version 确认
bash -c 'BASH_ARGV0=echo; "$0" ran as echo'

```

按手册，`$0` 变成 `echo`，第二条语句执行 `echo ran as echo`。如果 `bash --version` 低于 5.0，`BASH_ARGV0` 只是普通变量，`$0` 保持 shell 自己的名字，这一行会失败而不是回显。

检查器要拦住它，必须知道：**这个变量会写另一个变量，只在 bash 里，而且只从 5.0 开始**。2.1.296 的条目说，现在对"某些这种形状"的命令会要求批准——对一个"文本里没有程序名"的命令，这是正确的默认值。

## 六、加固：把规则当过滤器，把沙箱当墙

先做最基础的一步：**看版本**。

```bash
claude --version

```

上面所有修复都在 **2.1.296 或更新**版本里。低于这个版本，先升级，再谈配置。

然后改变对"规则"的期待。**Bash deny 规则是对"模型通常写出的文本"的过滤器**，这四次修复说明：要让文本过滤器变成一堵墙，每补一个洞就得教会它一条 shell 规则，而 shell 的规则远不止四条。**需要"一定成立"的限制，得加一层不读文本的东西。**

沙箱是由操作系统强制执行的（macOS 的 Seatbelt、Linux 与 WSL2 的 bubblewrap），限制的是**命令能碰到哪些文件和主机**，不管命令怎么拼。但它只覆盖 shell 命令：**Claude 的文件工具、MCP server、hooks 都在沙箱之外**，走普通权限规则。

一份可用的加固配置：

```json
{
  "permissions": {
    "deny": ["Bash(rm *)", "Bash(git push *)"]
  },
  "sandbox": {
    "enabled": true,
    "autoAllowBashIfSandboxed": false,
    "allowUnsandboxedCommands": false,
    "filesystem": {
      "denyRead": ["~/.ssh", "~/.aws"]
    }
  }
}

```

四个开关分别做什么：

| 配置                              | 作用                                                                 | 代价            |
| ------------------------------- | ------------------------------------------------------------------ | ------------- |
| autoAllowBashIfSandboxed: false | 关掉"在沙箱里就自动放行"，让 deny/ask 漏掉的形状仍然走弹窗（默认是 true）                      | 弹窗变多          |
| allowUnsandboxedCommands: false | 忽略 dangerouslyDisableSandbox，命令在沙箱里失败也不能"关掉沙箱重试"                   | 某些工具会在沙箱里跑不起来 |
| filesystem.denyRead             | 默认沙箱命令能读"机器的大部分"，包括 \~/.ssh、\~/.aws/credentials；这条对命令启动的**每个进程**生效 | 依赖这些凭据的构建会失败  |
| 保留 deny 规则                      | 仍然能挡住模型日常写出的普通 rm -rf build                                        | 无             |

注意 `autoAllowBashIfSandboxed: false` 只覆盖了 2.1.289 那两类的**形状**，它**不改变**"判断命令安全"的那条独立检查——2.1.290 与 2.1.296 的修复就住在那条检查里。对它们，**只有升级有用**。

## 七、自测：把你自己的规则打一遍

升级完之后，用下面这些**无害**的命令检查"该弹窗的有没有弹窗"。不要用真的 `rm`，把它们换成 `echo`：

```bash
# 1) 前缀赋值：规则有没有"看到"后面的命令
FOO=bar echo should-be-checked

# 2) 展开值的前缀
TZ="$HOME" echo should-be-checked

# 3) 裸赋值 + 命令
X=1; echo should-be-checked

# 4) zsh 方言（在 zsh 会话里跑）
path=(/nowhere); command -v ls || echo no-ls

# 5) BASH_ARGV0（bash 5.0+）
bash -c 'BASH_ARGV0=echo; "$0" ran as echo'

```

判据是**过程**而不是结果：这些命令本身不危险，关键是**它们有没有经过你期望的那道检查/弹窗**。如果某一条被静默自动批准，说明你的版本或配置还留着缺口。

## 八、一张图收尾：三层防线，各管一段

| 层                  | 管什么             | 不管什么                 | 失效时的表现          |
| ------------------ | --------------- | -------------------- | --------------- |
| **文本规则（deny/ask）** | 模型**通常**怎么写命令   | 任意 shell 方言、路径别名、包装器 | 换个写法就绕过（本文四例）   |
| **OS 沙箱**          | 命令能碰到的文件与网络     | 文件工具、MCP、hooks       | 越界被拒，但合法范围内仍可破坏 |
| **审计日志 / 逐轮 diff** | 事后知道**实际发生了什么** | 事前阻止                 | 只能追责与恢复         |

这三层不是替代关系，而是**互不重叠的覆盖**：规则便宜、沙箱硬、日志可追。作者的一句话值得抄下来：**deny 规则在命令运行前做决定，逐轮 diff 告诉你命令实际改了什么，不管它的文本怎么写的。**

最后是边界：本文讨论的是**权限检查的形状**，不是 Claude Code 的内部实现；changelog 没有说明是谁发现的、是否有人真的踩到、检查器内部错在哪。作者明确不猜内部，只按 shell 手册推演——这个态度本身就值得学。任何"我的 deny 规则很安全"的结论，都应该用你自己的命令集去验证一遍。

---

**Sources**

- Gabriel Anhaia, *4 Ways a Shell Variable Slipped Past Claude Code's Permission Checks*, dev.to, 2026-10-18：[https://dev.to/gabrielanhaia/4-ways-a-shell-variable-slipped-past-claude-codes-permission-checks-24b9](https://dev.to/gabrielanhaia/4-ways-a-shell-variable-slipped-past-claude-codes-permission-checks-24b9?ref=hubwiz.com)
- Claude Code CHANGELOG（v2.1.289 / 2.1.290 / 2.1.296）：[https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md?ref=hubwiz.com)
- 权限与沙箱文档：[https://code.claude.com/docs/en/permissions](https://code.claude.com/docs/en/permissions?ref=hubwiz.com)、[https://code.claude.com/docs/en/sandboxing](https://code.claude.com/docs/en/sandboxing?ref=hubwiz.com)
- bash 手册（SIMPLE COMMAND EXPANSION、BASH\_ARGV0）：[https://man7.org/linux/man-pages/man1/bash.1.html](https://man7.org/linux/man-pages/man1/bash.1.html?ref=hubwiz.com)
- zsh 手册（`path` 参数）：[https://zsh.sourceforge.io/Doc/Release/Parameters.html](https://zsh.sourceforge.io/Doc/Release/Parameters.html?ref=hubwiz.com)

**相关阅读**：[Agent / MCP / 自动化完全指南](https://www.hubwiz.com/blog/agent-stack/) ｜ [Agent 安全：九起事故与六项控制](https://www.hubwiz.com/blog/agent-security-nine-incidents-six-controls/) ｜ [MCP 供应链体检：15,341 个服务器扫描](https://www.hubwiz.com/blog/mcp-registry-supply-chain-audit/)

本文由汇智网编译整理，转载请注明出处。文中配置与命令示例需按你的实际版本与环境验证，不构成安全保证。

> **这是「Agent / MCP」专题的一部分。**完整地图见：[Agent / MCP / 自动化完全指南](https://www.hubwiz.com/blog/agent-stack/)