Claude Code 权限与安全:模式、沙箱与 settings.json

Posted on Sun 27 September 2026 in AI 编程 • Sucre Liu

先分清两件事

Claude Code 的安全模型有两层,经常被混为一谈:

  • 权限模式决定"要不要问你就做";
  • 隔离边界(沙箱、容器)决定"做的时候能碰到什么"。

模式再紧,命令一旦跑起来还是能碰到它能碰到的;隔离再强,模式不对你也会被频繁打断。两层要分开设计。

六个模式,逐档看

配置值用括号标注,CLI 里按 Shift+Tab 循环切换:

模式 不问就执行 适合
Manual(default) 只读 敏感代码、陌生仓库
acceptEdits 读 + 改文件 + 常见文件系统命令 你打算事后看 git diff
plan 只读 + 分类器放行的命令 动手前先出方案
auto 几乎全部,后台分类器把关 长任务、减少打断
dontAsk 只读 + 预先批准的工具,其余一律拒绝 锁死的 CI/脚本
bypassPermissions 全部 仅限隔离容器/虚拟机

几个容易记错的点:

  • Manual 并不禁止读。读不改变任何东西,所以任何模式都放行。
  • 模式是基线,权限规则叠在上面。deny 规则在任何模式都生效,包括 bypassPermissions。
  • 反过来,allow 规则在 bypassPermissions 下没有意义——因为它本来就全放。

任何模式都不会自动放行的动作

这是很多人误以为"开了 auto 就万事大吉"的地方。以下动作在所有模式下都不会被自动批准:

  • 被显式 ask 规则命中的工具;
  • 需要用户交互的工具(比如它向你提问的那个);
  • 针对关键路径的 rm / rmdir(如 rm -rf /、rm -rf ~);
  • 开启 blockReadsOutsideWorkingDirectories 后,对工作目录外路径的读取。

也就是说,有些闸门是设计上焊死的,切换模式也绕不过去。这是好事。

acceptEdits 到底放行了什么

这档的自动化范围比字面意思广一些。它在工作目录内会自动批准:

  • 文件创建与编辑;
  • 一组常见文件系统命令:mkdir、touch、rm、rmdir、mv、cp、sed;
  • 这些命令带安全前缀(如 timeout、nice、nohup、LANG=C)也算。

但工作目录之外的路径、受保护路径、关键路径的删除、以及其余所有 Bash 命令,仍然会问。所以 acceptEdits 适合"我信任它在项目里改,改完我自己看 diff",不适合"我不在乎它跑什么"。

plan 模式:把方案当成可审查的产物

Shift+Tab 切换到 plan,或给单条提示加 /plan 前缀。它会读文件、跑探索命令、写方案,但不改你的源码。

方案出来后会给你三个选择:

  • 是,并用 auto:批准并进入 auto 模式开始改;
  • 是,逐条批准:批准但每次改动仍问你;
  • 不,继续规划:留在 plan 里,告诉它改哪里。

有个不太被注意的功能:按 Ctrl+G 可以把方案拉到你的默认编辑器里直接改,再让它执行。对复杂任务,先改方案比事后改代码便宜得多。

auto 模式:分类器挡了什么

auto 模式用一个独立的分类器模型在后台审每个动作,替你把关,减少打断。它默认信任你的工作目录和会话开始时已配置的远程,其余视为外部。默认拦截的典型动作包括:

  • curl | bash 这类"下载并执行代码";
  • 向外部端点发送敏感数据;
  • 生产环境部署与迁移;
  • 云存储上的批量删除;
  • 授权 IAM 或仓库权限;
  • git push --force、git reset --hard、git clean -fd;
  • 对已在会话外推送过的提交做 --amend;
  • terraform destroy / pulumi destroy 这类销毁式操作;
  • 未获批就合并 PR、关闭 CI 检查;
  • 把凭据/令牌打印进记录或文件。

但官方的警告必须照抄一遍:auto 减少打断,但不保证安全。 它适合"大方向我信得过"的任务,不能替代你对敏感操作的审查。而且它只在满足条件时可用:支持的套餐、模型(如 Sonnet 4.6+、Opus 4.6+)和 provider;组织也可以直接禁用。

把规则固化进 settings.json

模式是临时选择,规则是长期资产。常用的两个方向:放行可信命令、禁掉危险路径。

放行 npm test、git status 这类不需要每次确认:

{
  "permissions": {
    "defaultMode": "default",
    "allow": ["Bash(npm test)", "Bash(git status)", "Bash(git diff *)"],
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Bash(curl *)"]
  }
}

几个必须知道的细节:

  • deny 是硬的:它在所有模式生效,bypassPermissions 也拦得住(少数内置交互工具除外)。
  • defaultMode: "auto" 不认项目级设置。如果你发现设了 auto 却还是 Manual,多半是写在了 .claude/settings.json;把它移到 ~/.claude/settings.json。
  • bypassPermissions 同样不认项目级设置,写在项目里会退回 Manual。这是防止仓库"自我放权"的设计。

配置作用域从组织托管策略到个人偏好,优先级由近到远。写敏感放行规则前,先想清楚它会不会被提交给团队。

硬拦截:钩子

settings.json 的允许/拒绝列表能覆盖大部分情况,但有些规则想带条件、带提示语,就该上钩子。一个最小的"保护敏感文件"示例——拦截对 .env 的编辑,并把原因回给模型:

#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
FILE_PATH="${FILE_PATH//\\//}"

for pattern in ".env" "package-lock.json" ".git/"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH 命中保护模式 '$pattern'" >&2
    exit 2   # exit 2 = 拦截,stderr 作为反馈给模型
  fi
done
exit 0

注册为 PreToolUse,匹配 Edit|Write:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh" }
        ]
      }
    ]
  }
}

注意 exit 2 的语义:拦截动作,并把 stderr 作为反馈交给模型,它会据此换一条路走——这正是"拒绝"和"无声失败"的区别。钩子的完整用法放在下一篇。

沙箱与容器

权限模式管"问不问",沙箱管"能碰到什么"。内置的沙箱 Bash 工具在 macOS/Linux/WSL2 上提供文件系统与网络隔离;更彻底的隔离是容器或虚拟机。选隔离级别,看你的威胁模型:

  • 只是怕误删/误提交 → 模式 + 规则 + 检查点就够;
  • 要跑不可信的自动化 → 上容器,并且以非 root 用户运行。

bypassPermissions 只在最后那种场景里出现,而且要配合容器。裸机裸仓库用它,是把保险丝换成铜丝。

检查点救不了的事

再提醒一次第 1 篇讲过的边界:Esc Esc 的检查点只能回退文件改动,而且不覆盖符号链接/硬链接。影响远端系统的动作——数据库、API、部署——无法回退。 那些只能靠模式和规则提前挡住。

所以这份安全配置的优先级是:

  1. 敏感操作不上 bypassPermissions;
  2. 用 deny 把秘密文件和危险命令焊死;
  3. 用钩子处理需要条件和反馈的规则;
  4. 剩下的用模式控制打断频率。

小结

  • 模式定基线,规则叠在上面;deny 在所有模式生效,allow 在 bypass 下无效。
  • 关键路径 rm、交互式工具等,永远不自动放行。
  • acceptEdits 会连带放行一批文件系统命令,注意范围。
  • auto 挡的是经典危险动作,但不等于安全。
  • 项目级设置无法开启 auto / bypassPermissions,这是有意的。

下一篇进入扩展层:CLAUDE.md、Skill、Subagent、Hook、MCP 到底该怎么选,以及各自最小的可运行示例。