2-权限设计

规划了一个长任务,结果总是需要授予权限而不能脱身; 授予完全权限又害怕删盘跑路……

Anthropic 官方工程博客披露:用户实际批准了 93% 的权限提示。社区把这种状态命名为 permission fatigue(权限疲劳)——「几秒钟点一次批准,最后你根本不读就批了」,这本身就是安全风险。 「全权放手又怕删盘」 —— --dangerously-skip-permissions 模式清空了 home 目录、抹掉整台 Mac删了生产数据库记录,这些都是真实的事故。

其实如今的 Claude Code 权限系统在两端之间提供多个档位,只要善于利用,就可以做到解脱于权限确认,也不怕事故出现,能够大大提升长任务的效率和安全性。

目录


1.1 一次工具调用的权限流程

权限规则由 Claude Code(harness层)强制执行,不是由模型决定。这是整个权限设计的根基。

一次工具调用(如 Bash("rm -rf /tmp/x"))从发起到执行,权限系统按以下顺序介入:

代码
                  权限模式初始化(CLI 参数 / settings 加载)
                                 │
                                 ▼
                          一次工具调用
                                 │
                                 ▼
               ┌────────────────────────────────────┐
               │   ① 规则评估                        │  ← deny/ask 在所有模式生效
               │   deny → ask → allow               │
               └─────────────────┬──────────────────┘
            命中 │                       │没命中任何规则
                ▼                        ▼
          deny → 阻止             按 mode 处置(六种):
          ask  → 询问             bypassPermissions → 断路器(rm -rf / ~)→ 其余放行
          allow→ 放行             acceptEdits       → 自动接受(受保护路径写入触发提示)
                │                default           → 向用户询问
                │                plan              → 禁写
                │                auto              → 分类器自主判断
                │                dontAsk           → 自动拒绝
                │                                │
                └────────────────┬───────────────┘
                                 ▼
                         所有放行 / 批准
                                 │
                  ┌──────────────┴──────────────┐
                非 Bash                       Bash
                  │                             │
                  ▼                             ▼
      ┌──────────────────────┐   ┌────────────────────────────────────┐
      │ 直接执行              │   │ ② 沙箱兜底(OS 级,仅 Bash)         │
      │ Read/Edit/Write/     │   └─────────────────┬──────────────────┘
      │ Grep/Glob/WebFetch/  │                     ▼
      │ MCP/Task…            │               执行 / 阻止
      └──────────────────────┘
  • 受保护路径:写 .git/.claude 等关键路径,acceptEdits 等放权模式也照样提示;bypass 放行。
  • 规则 = 硬约束:deny/ask 永远生效,压过一切模式。这就是为什么 ask 规则即使在 bypassPermissions 下也照样弹询问,deny 更是任何模式下都会拦截。

权限与沙箱是两层互补防御

  • 权限——控制 Claude 能用哪些工具、访问哪些文件/域。作用于所有工具(Bash、Read、Edit、WebFetch、MCP 等)。
  • 沙箱——OS 级强制,限制 Bash 工具的文件系统与网络访问。仅作用于 Bash 及其子进程。

1.2 权限管理都在 settings.json 里

Claude Code 的所有权限配置都写在 settings.json 里,权限相关的字段集中在顶层 permissions 对象下:

json
{
  "permissions": {
    "defaultMode": "acceptEdits",        // 权限模式(见 1.3)
    "allow": ["Bash(npm run test:*)"],   // 规则三态(见 1.4)
    "ask":   ["Bash(git push *)"],
    "deny":  ["Read(./.env)"],
    "additionalDirectories": ["../docs/"]
  },
  "sandbox": { "enabled": true }         // 沙箱(见 1.6)
}

而且它不是单个文件,而是分层的一组:Claude Code 按固定层级合并——managed(企业强制、最高优先级、不可覆盖)→ 用户级 ~/.claude/settings.json → 项目级 <项目>/.claude/settings.json(进 git、团队共享)→ 本地级 .claude/settings.local.json(gitignore、个人覆盖)。


1.3 权限模式与设定途径

1.3.1 六种模式

根据官方文档,settings.jsonpermissions.defaultMode 字段决定 Claude Code 启动时的初始权限模式,共有六个可选值,沿自主性↔安全性分布如下:

代码
自主性低(安全)                                              自主性高(放手)
dontAsk ── default ── plan ── acceptEdits ── auto ── bypassPermissions
模式行为
dontAsk只放行 permissions.allow 规则集里的,未预批准的工具自动拒绝,连问都不问
default每个工具首次使用时询问
plan只读,不编辑源文件(写操作被禁,非询问)
acceptEdits自动接受工作目录或 additionalDirectories目录中的文件编辑命令、 固定的文件系统命令(mkdir/touch/mv/cp/rm/rmdir/sed),其他 Bash 仍询问
auto分类器自动批准多数工具调用,后台安全检查验证动作符合请求(研究预览
bypassPermissionsask 规则依旧询问,deny 规则直接阻断,断路器(rm -rf //rm -rf ~)最后一步拦截;其余全部放行,包括受保护路径
  • auto 受账户计划/模型/提供商限制,第三方 API 未必可用,本文不展开。

  • 任意模式都压不住 deny / 显式 ask

1.3.2 设定权限模式的三种途径

途径字段/方式作用域
settings.jsonpermissions.defaultMode每次启动的初始模式
CLI 启动参数--permission-mode xxx单次会话覆盖 defaultMode
Shift+Tab / Alt+M会话中按键当次临时切换

字段值/cli参数: acceptEdits, auto, bypassPermissions, default, dontAsk, plan.

优先级CLI 参数 > defaultModeShift+Tab 只在运行时临时改、不落盘)。

作用域限制

  • 项目/本地级写 "auto" 被忽略(v2.1.142+,防仓库给自己授权)。
  • 云会话里 "dontAsk"/"bypassPermissions" 也被忽略。
  • "bypassPermissions":项目/本地级允许生效(本地),但强制走确认提示。

别名--dangerously-skip-permissions--permission-mode bypassPermissions(同物旧名)。

Shift+Tab(运行时切换)——基础循环 default → acceptEdits → planauto/bypassPermissions 是可选模式,启用后插在 plan 之后。


1.4 规则三态与评估顺序

settings.jsonpermissions 对象:

json
{
  "permissions": {
    "allow": ["Bash(npm run test:*)"],
    "ask":   ["Bash(git push *)"],
    "deny":  ["Read(./.env)", "Bash(curl *)"]
  }
}

三条评估铁律:

  1. 顺序:deny → ask → allow,首个匹配即定论,特异性不改变顺序
  2. 匹配的 ask 即使有更具体的 allow 同时匹配,仍询问
  3. 裸工具名 deny(如 Bash)把工具从模型 context 完全移除——模型看不见它,无从调用;作用域 deny(如 Bash(rm *))保留工具,但阻止匹配调用。

1.5 受保护路径

一小批「写它就提示」的关键路径,在除 bypassPermissions 外的所有模式下都拦,防 Agent 静默损坏仓库状态Claude 自身配置。安全检查发生在 allow 规则之前,所以连显式 Edit(.claude/**) 也压不住它。

重点三类(按防护目的):

  • 仓库状态.git / .gitconfig / .gitmodules——防静默篡改历史、版本控制配置
  • Claude 自身配置.claude / .mcp.json / .claude.json——防 Agent 给自己加 allow/bypass、改 MCP 配置
  • Shell rc.bashrc / .zshrc / .profile 等——防写 rc 做跨会话持久化 / 注入

其余还有一批配置文件:包管理器(.npmrc / .yarnrc…)、构建工具(.bazelrc / gradle-wrapper.properties…)、Git hooks(.pre-commit-config.yaml / lefthook.yml…)、IDE(.vscode / .idea)、.devcontainer.ripgreprc / pyrightconfig.json 等,以及 .config/git / .husky / .cargo / .yarn / .mvn 等目录。

.claude/worktrees 是例外——那是 Claude 自己的 git worktree 存储,不在保护范围内。

各模式处置:

模式受保护路径写入
default / acceptEdits / plan提示
auto交分类器判定
dontAsk直接拒绝
bypassPermissions放行(v2.1.126 起)

1.6 实用组合:两套拿来就能用的搭配

1.6.1 安全放手编辑:acceptEdits

acceptEdits 听着像「大撒手」,实际自主性并不高——默认放行的只有一小撮:

默认通过(不用确认):

  • 文件编辑Edit / Write / NotebookEdit 三个工具,路径在工作目录或 additionalDirectories
  • 7 个 Bash 文件系统命令mkdir / touch / mv / cp / rm / rmdir / sed(可带 LANG=C 等环境变量前缀、timeout / nice / nohup 包装器)

仍需确认(高频但不在上面名单里):

  • Skill 调用MCP 工具调用——都属「需权限」工具,acceptEdits 不覆盖
  • 构建 / 测试 / 装包npm installnpm testgo build… 既不在只读集、也不在 7 命令名单
  • 写操作 gitgit commitgit push
  • WebFetch 首次访问新域名

注意区分:ls / cat / grep / cd / 只读 git(status/log)这些在所有模式(含 default)下本就不询问——那是「只读命令集」的功劳,不算 acceptEdits 放的权。

所以 acceptEdits 真正省下的,主要是编辑文件那 7 个文件系统命令的反复确认;其余高频操作仍要逐个批。配合受保护路径(见 1.5)锁住危险文件,编辑类长任务可安全放手。

json
{ "permissions": { "defaultMode": "acceptEdits" } }
代码
claude --permission-mode acceptEdits

1.6.2 受控的危险模式:bypass + deny

bypass 跳过所有提示和安全检查(含受保护路径,自 v2.1.126 起也放行),几乎一切立即执行。仍能拦住的只剩三样:deny 规则直接阻断显式 ask 规则仍提示断路器rm -rf /rm -rf ~ 针对根/home 本身的删除仍提示,防模型手滑)。

实战套路:在隔离环境(容器/VM)里开 bypass,再用 deny 锁死绝不允许的、ask 拦下要审一下的:

json
{
  "permissions": {
    "deny": [
      "Read(~/.ssh/**)", "Read(~/.aws/**)", "Read(~/.gnupg/**)",
      "Read(~/.kube/config)", "Read(~/.docker/config.json)", "Read(~/.npmrc)",
      "Read(.env)", "Read(**/.env.*)", "Read(**/*.pem)", "Read(**/*.key)", "Read(**/id_rsa*)",

      "Bash(git push --force*)", "Bash(git push -f *)", "Bash(git push -f)",
      "Bash(git push * --force*)", "Bash(git push * -f *)",
      "Bash(git push --delete *)", "Bash(git push --mirror*)",

      "Bash(rm -rf *)", "Bash(rm -r *)", "Bash(rm -fr *)",
      "Bash(rm -rf .)", "Bash(rm -rf ./*)",
      "Bash(find * -delete)", "Bash(find * -exec rm*)",

      "Bash(mkfs*)", "Bash(dd *)", "Bash(shred*)",

      "Bash(psql * DROP*)", "Bash(psql * drop*)",
      "Bash(mysql * DROP*)", "Bash(mysql * drop*)",
      "Bash(redis-cli *flush*)",

      "Bash(sudo *)", "Bash(su *)",

      "Edit(~/.bashrc)", "Edit(~/.zshrc)", "Edit(~/.profile)"
    ],
    "ask": [
      "Bash(curl *)", "Bash(wget *)", "Bash(nc *)", "Bash(netcat *)",

      "Bash(rm *)",

      "Bash(npm install *)", "Bash(npm i *)", "Bash(pip install *)",
      "Bash(pip3 install *)", "Bash(yarn add *)", "Bash(pnpm add *)",

      "Edit(.claude/**)", "Edit(**/.mcp.json)", "Edit(**/.claude.json)", "Edit(~/.claude/**)",

      "Edit(**/.gitconfig)", "Edit(~/.gitconfig)"
    ]
  }
}

启动该模式:

bash
claude --permission-mode bypassPermissions
# 等价别名(旧名,同物)
claude --dangerously-skip-permissions

1.7 沙箱:OS 级兜底

Claude Code 放行 Bash 的执行权限后,OS 层还会再拦一道。

沙箱是什么——Claude 跑 Bash 命令时,OS 在这个进程外面套一层"笼子":它碰到的每一次读文件、写文件、联网,OS 都拿你设的边界过一遍,越界直接拦掉,跟 Claude 想不想跑无关。macOS 用内置 Seatbelt,Linux/WSL2 用 bubblewrap,Windows不支持。

一个拦截例子——Claude 跑 npm install some-package 被批准,但这包的安装脚本里藏了"读 ~/.ssh/id_rsa → POST 到 evil.com"的逻辑

  • 没沙箱npm 用你本人的权限在跑,能读你所有文件、能随便联网,脚本读出私钥发走,私钥泄露
  • 有沙箱:命令照样跑,但跑在笼子里——读 ~/.ssh/id_rsa 被 OS 拦(denyRead)、连 evil.com 被拦(不在 allowedDomains),脚本死掉,私钥没事

这就是"兜底"的真意:就算提示注入骗过模型决策、就算权限放行了,OS 这层照样按物理边界拦。

字段就是笼子的"一面墙"或"一个开关":

json
{
  "sandbox": {
    // 总闸
    "enabled": true,
    // 关键:笼子既然兜得住,就别逐条弹 Bash 提示了——
    // 心智从"每条审批"切到"只管笼子边界"。
    // settings.json中的 ask(如  ["Bash(git push *)"])、显式 deny;
    // 以及针对 / 和 home 目录的 rm 命令仍会拦截并提示。
    "autoAllowBashIfSandboxed": true,
    // 必须放出笼子跑:docker、或 gh/gcloud/terraform 这类
    // Go 系 CLI(macOS Seatbelt 里 TLS 验证会挂)
    "excludedCommands": ["docker *"],
    // 是否允许 Claude 用 dangerouslyDisableSandbox 紧急跳出
    // 重试;企业要"绝不脱笼"设 false
    "allowUnsandboxedCommands": true,
    "filesystem": {
      // 不准读:私钥、整个 .aws 目录
      "denyRead": ["~/.ssh/**", "~/.aws"],
      // 不准写
      "denyWrite": ["/etc", "/usr/local/bin"],
      // 在 ~/.aws 禁区里再开一个小口读 config(压过 denyRead)
      "allowRead": ["~/.aws/config"],
      // 默认工作目录之外,额外允许写的路径
      "allowWrite": ["/tmp/build", "~/.kube"]
    },
    "network": {
      // 白名单:只放行装包要用的,其余默认拦
      "allowedDomains": ["github.com", "*.npmjs.org"],
      // 黑名单命中优先于 allowed(deny 胜)
      "deniedDomains": ["*.evil.com"]
    }
  }
}

要点:

  • 沙箱 filesystem 的 allow 可以在 deny 里开小口(特指 allowRead/allowWrite):allowRead 优先于 denyReadallowWritedenyWrite 同理。典型套路:denyWrite 整个 home、再 allowWrite: ["."] 只开工作目录。
  • 权限规则的 deny 路径会自动灌进沙箱:在 permissions.deny 里写一条 Read(./.env)(见 1.4),它既 挡住 Claude 自己的 Read 工具,路径 .env 又被并入沙箱 denyRead——一条规则两层生效。反向不成立:单写在 sandbox.filesystem.denyRead 里的路径只拦子进程,挡不了 Claude 自己的 Read。
  • network 的黑名单级别最高deniedDomains 压过 allowedDomains。⚠️ allowUnixSockets 慎用——放行 /var/run/docker.sock 等于把宿主机交给容器。

参考来源(官方文档,截至 2026-06)

版本锚点:受保护路径在 bypass 下放行自 v2.1.126 起;defaultMode: "auto" 在项目/本地 settings 被忽略自 v2.1.142 起;Bedrock/Vertex/Foundry 的 CLAUDE_CODE_ENABLE_AUTO_MODEv2.1.158 起。