# 权限模式与安全：放手但不失控

> 确认弹窗点到第二十次，你就开始无脑点「是」了。这一课讲清六种权限模式、allow / ask / deny 规则的写法和 Bash 沙箱，以及怎样按场景在安全与效率之间取舍。

- 网址：https://coding.chengyu.dev/learn/claude-code/permissions/
- 学习路径：Claude Code 精通
- 预计用时：20 分钟；核实日期：2026-10-25
- 官方来源：[Choose a permission mode（Claude Code 官方文档）](https://code.claude.com/docs/en/permission-modes)，[Configure permissions（Claude Code 官方文档）](https://code.claude.com/docs/en/permissions)，[Settings files and precedence（Claude Code 官方文档）](https://code.claude.com/docs/en/settings)，[All settings（Claude Code 官方文档）](https://code.claude.com/docs/en/settings-reference)，[Configure the sandboxed Bash tool（Claude Code 官方文档）](https://code.claude.com/docs/en/sandboxing)

## 学习目标

- 能说出六种权限模式各自放行什么，并用 Shift+Tab 或启动参数切换
- 能写出正确的 allow、ask、deny 规则，知道它们的判定顺序
- 能说清沙箱管什么、不管什么，并在本机开启
- 能为日常开发、陌生仓库和 CI 三种场景选出合适的配置

用 Claude Code 的人大致会经历两个极端。一开始，每条 `pnpm test` 都弹窗确认，点到第二十次，你已经不看内容直接回车了，这时的「确认」形同虚设。另一个极端是嫌麻烦，直接用 `--dangerously-skip-permissions` 全部放开，直到某天它顺手执行了一条你没想到的命令。

这两种都不对。Claude Code 提供了一整套分层的控制手段：**权限模式**定基调，**权限规则**做精细放行和拦截，**沙箱**在操作系统层面兜底。这一课把三者讲清楚。

## 权限确认：它在问你什么

默认情况下，不同类型的操作有不同的审批要求：

| 操作类型 | 例子 | 是否需要批准 | 选「是，且不再询问」的效果 |
|---|---|---|---|
| 只读 | 读文件、搜索 | 不需要（限工作目录内） | 无 |
| Bash 命令 | 运行 shell 命令 | 需要，内置只读命令除外 | 对该仓库和该命令永久生效 |
| 修改文件 | 编辑、写入 | 需要 | 本次会话结束前有效 |
| 网页抓取 | WebFetch | 需要，部分预批准的文档域名除外 | 对该仓库和该域名永久生效 |

`ls`、`cat`、`grep`、`git status` 这类内置只读命令不会弹窗。你选「是，且不再询问」时，规则会写进仓库根目录的 `.claude/settings.local.json`，以后在这个仓库里都生效。在弹窗里选中「是」或「否」后按 `Tab`，还可以附一句话给 Claude，比如「可以跑，但下次别加 `--force`」。

## 六种权限模式

| 模式 | 无需询问就能做 | 适合 |
|---|---|---|
| `default`（界面上叫 Manual） | 只有读取 | 敏感工作、陌生代码，每步都自己看 |
| `acceptEdits` | 读取、改文件、`mkdir` `mv` `cp` 等常见文件命令 | 一边让它改，一边在编辑器或 `git diff` 里审 |
| `plan` | 读取和探索，不改源码 | 动手前先摸清代码、出方案 |
| `auto` | 几乎所有操作，由后台分类器把关 | 长任务、减少打扰 |
| `dontAsk` | 只做读取和你预先批准的工具，其余一律拒绝 | CI 和脚本 |
| `bypassPermissions` | 全部 | 仅限隔离的容器或虚拟机 |

切换方法：

- **会话中**按 `Shift+Tab` 循环切换，状态栏会显示 `⏸ manual mode on`、`⏵⏵ accept edits on`、`⏸ plan mode on`、`⏵⏵ auto mode on` 等；
- **启动时**用参数，如 `claude --permission-mode plan`；
- **设为默认**在设置文件里写 `permissions.defaultMode`。

几个模式值得展开说：

**auto 模式。** 从 v2.1.283 起，只要你的账号和模型支持，交互式终端和 VS Code 会话的内置默认模式就是 auto（`claude -p` 等非交互运行默认仍是 Manual）。它不再逐条问你，而是由一个独立的分类器模型在执行前审查。默认会拦下的包括：`curl | bash` 式的下载即执行、把敏感数据发到外部、生产环境部署和迁移、强制推送、`git reset --hard` 这类丢弃未提交改动的命令、打印密钥等。同一操作连续被拦 3 次或累计 20 次，会暂停 auto、恢复弹窗。你在对话里说「先别 push」，分类器也会当成边界执行；但这句话如果在上下文压缩时被丢掉，约束就没了，所以**真正的硬约束要写成 deny 规则**。官方也明说：auto 模式减少弹窗，但不保证安全。

**plan 模式。** Claude 可以读文件、跑探索性命令、写方案，但不改你的源码；方案确认前，编辑一直被拦着。也可以只给单条消息加 `/plan` 前缀。方案出来后可以选「是，并用 auto 模式」「是，手动批准每次编辑」或「否，继续规划」，按 `Ctrl+G` 能在编辑器里直接改方案。改动涉及多个文件、你对方案没把握时，先 plan 很值。

**bypassPermissions 模式。** 跳过几乎所有检查，等价于 `--dangerously-skip-permissions`。官方要求只在无法伤害宿主机的隔离环境里用，比如断网的容器或虚拟机。

> **defaultMode 写在哪里有讲究**
>
> `"defaultMode": "auto"` 写在项目的 `.claude/settings.json` 或 `.claude/settings.local.json` 里不会生效，要写在 `~/.claude/settings.json`；`bypassPermissions` 同样不能从项目文件里开启。这是为了防止别人提交的仓库配置替你放开权限。

## 权限规则：allow、ask、deny

模式定基调，规则做精细控制。规则写在设置文件的 `permissions` 下，格式是 `工具名` 或 `工具名(说明符)`：

```json
{
  "permissions": {
    "allow": [
      "Bash(pnpm test *)",
      "Bash(pnpm lint)",
      "Bash(git diff *)",
      "WebFetch(domain:vuejs.org)"
    ],
    "ask": [
      "Bash(git push *)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./config/oss-keys.json)",
      "Bash(curl *)"
    ]
  }
}
```

这份配置的意思是：测试、lint、看 diff 不用问；推送前一定要问；不许读 `.env` 和存阿里云 OSS 密钥的文件，也不许用 curl。

规则的要点：

- **判定顺序是 deny → ask → allow，第一个匹配的说了算，和写得多具体无关。** 写了 `deny: Bash(aws *)`，再写 `allow: Bash(aws s3 ls)` 也放不出来。
- **`*` 匹配任意文本，要放在子命令后面。** `Bash(git log *)` 只放行 `git log`，`Bash(git *)` 则放行所有 git 命令，包括 push。`Bash(ls *)` 不匹配 `lsof`，`Bash(ls*)` 会匹配。
- **复合命令逐段匹配。** `Bash(pnpm test *)` 不会放行 `pnpm test && rm -rf dist`，每一段都要单独过关。
- **路径写法有锚点。** 在项目设置里，`./` 或 `/` 开头相对于项目目录；`//` 开头是绝对路径；`~/` 是家目录。注意 `Read(/secrets/**)` 写在用户设置里时指的是 `~/.claude/secrets/`，不是你项目里的 secrets 目录。
- **Bash 参数级的限制很脆弱。** 想限制 curl 只访问某个域名，换个参数顺序、换个协议就绕过去了。更可靠的做法是 deny 掉 curl，用 `WebFetch(domain:...)` 放行允许的域名，再配合沙箱的网络白名单。

规则放在哪个文件，决定了对谁生效：

| 文件 | 影响范围 | 适合放 |
|---|---|---|
| `~/.claude/settings.json` | 你在本机的所有项目 | 个人通用规则 |
| `.claude/settings.json` | 项目里所有人（提交到 git） | 团队共享的规则 |
| `.claude/settings.local.json` | 只有你、只在这个项目 | 个人例外，弹窗里选的规则也存在这里 |
| 组织托管设置 | 组织部署到的所有机器 | 安全策略，其他层级无法覆盖 |

同名的普通配置项按优先级取值：托管设置 > 命令行 > 项目本地 > 项目共享 > 用户。但 `allow`、`deny` 这类列表会**跨文件合并**，并且任何一层的 deny 都没法被别的层的 allow 放开。另外，项目 `.claude/settings.json` 里的 allow 规则要等你在信任弹窗里确认这个文件夹之后才生效，deny 和 ask 则立即生效。随时可以用 `/permissions` 查看所有规则和它们来自哪个文件。

还有一类**受保护路径**，如 `.git`、`.vscode`、`.husky`、`.claude` 目录和 `.zshrc`、`.npmrc`、`.mcp.json` 等文件。写入它们不会因为 allow 规则或 acceptEdits 模式被直接放行：Manual 和 acceptEdits 下会弹窗，auto 模式下交给分类器审查，dontAsk 下直接拒绝，只有 `bypassPermissions` 会放行。

> 小测：你的 settings.json 里写了 allow: [「Bash(git *)」]，deny: [「Bash(git push *)」]。Claude 执行 git push origin main 时会怎样？

## 沙箱：在操作系统层面兜底

规则是 Claude Code 自己执行的检查，沙箱则是交给操作系统强制的边界。运行 `/sandbox` 打开面板，选择模式即可开启：

- **自动允许模式**：在沙箱里跑的命令直接执行，不再弹窗。但 deny 规则照样拦截，`Bash(git push *)` 这类 ask 规则照样弹窗；
- **常规权限模式**：命令照样在沙箱里跑，但仍走正常的审批。

在面板里选的模式会存进项目的 `.claude/settings.local.json`，只对当前项目生效；想在所有项目里开启，在 `~/.claude/settings.json` 里设置 `sandbox.enabled` 为 `true`。

开启后，Claude 运行的 shell 命令及其子进程默认：只能写工作目录、一个临时目录和你用 `--add-dir` 加入的目录；能读机器上的大部分文件（包括 `~/.ssh` 这类凭据，需要用 `denyRead` 额外屏蔽）；不能直接联网，所有连接都经过本机代理按域名白名单检查。白名单初始为空，命令第一次访问新域名时会询问你（auto 模式下由分类器审查），也可以像下面这样预先放行：

```json
{
  "sandbox": {
    "enabled": true,
    "filesystem": {
      "denyRead": ["~/.ssh", "~/.aliyun"]
    },
    "network": {
      "allowedDomains": ["registry.npmmirror.com", "github.com"]
    }
  }
}
```

几个容易误会的地方：

- **沙箱只包住 shell 命令。** `Read`、`Edit`、`WebFetch` 这些内置工具、MCP 服务器和 hook 都在沙箱外运行，它们由权限规则管。`denyRead` 拦不住 `Read` 工具，读 `.env` 的限制还得写成 `Read` 的 deny 规则。
- **平台限制。** macOS 用系统自带的 Seatbelt，Linux 和 WSL2 需要先装 `bubblewrap` 和 `socat`；原生 Windows 不支持沙箱，要在 WSL2 里用。
- **不走代理的工具连不上。** 普通 `ssh` 和大多数数据库驱动不读代理变量，开了沙箱后，即使主机在白名单里，`mysql` 客户端这类工具也可能连不上，官方文档的排查部分有专门说明。
- **有逃生口。** 命令在沙箱里失败时，Claude 可能申请在沙箱外重跑。这次重跑不受沙箱保护，按你的权限模式审批：Manual 和 acceptEdits 下弹窗，auto 下交给分类器，`bypassPermissions` 下直接执行。想彻底关掉，在 `sandbox` 里设置 `"allowUnsandboxedCommands": false`。
- **启动失败时默认不拦。** 缺依赖或平台不支持导致沙箱起不来时，Claude Code 默认照常以非沙箱方式运行命令。把沙箱当安全底线的话，再设置 `"failIfUnavailable": true`，让它直接退出。

## 怎么取舍

| 场景 | 推荐组合 |
|---|---|
| 日常开发自己的项目 | auto 模式，或 Manual 加沙箱自动允许；再用 allow 放行测试、lint 命令 |
| 刚 clone 的陌生仓库 | 先用 plan 模式读懂代码；Manual 模式，逐条审批 |
| 涉及生产配置、密钥、数据库 | Manual 模式；把 `.env`、密钥文件、`git push` 写进 deny 或 ask |
| CI 和脚本 | `dontAsk` 加精确的 `--allowedTools`，详见第 9 课 |
| 要完全无人值守 | 只在容器或虚拟机里用 `bypassPermissions` |

原则只有一条：**必须守住的底线写成 deny 规则或 hook，其余的交给模式和白名单去减少打扰。** CLAUDE.md 里写「不要读 .env」只是请求，不是保障；需要在某个时机强制执行的检查，交给第 7 课的 hook。

> 小测：你在 CLAUDE.md 里写了「绝对不要读取 .env 文件」，同时开启了沙箱并在 sandbox.filesystem.denyRead 里加了 .env。Claude 用内置 Read 工具读 .env 时会怎样？

## 动手：为你的项目配一份权限

**生成权限配置草案**

```text
请为这个项目设计一份 .claude/settings.json 的 permissions 配置，先不要写入文件。

要求：
1. 先读 package.json（或 pom.xml、Makefile 等）和 CI 配置，找出日常开发会反复执行的测试、lint、构建命令，放进 allow，通配符放在子命令之后；
2. 找出项目里可能存放密钥或环境变量的文件（如 .env、各种 credentials、云服务密钥配置），为它们写 Read 的 deny 规则；
3. git push、数据库迁移、发布脚本这类有外部影响的命令放进 ask；
4. 对每条规则用一句话说明理由，并指出哪些规则更适合放在我个人的 settings.local.json 而不是提交给团队。

输出 JSON 和说明表格，等我确认后再写入。
```

- [ ] 按 `Shift+Tab` 走一遍所有模式，留意状态栏的变化
- [ ] 用 `claude --permission-mode plan` 打开一个陌生项目，只让它出方案
- [ ] 用上面的提示词生成规则，确认后写入 `.claude/settings.json`
- [ ] 为 `.env` 和密钥文件加上 `Read` 的 deny 规则，并让 Claude 尝试读取，确认被拒绝
- [ ] 在 macOS 或 Linux 上运行 `/sandbox` 开启沙箱，让 Claude 执行一次 `touch ~/sandbox-probe`，确认被阻止
- [ ] 运行 `/permissions`，检查每条规则来自哪个文件
