Claude Code 的权限模式你真的搞懂了吗?

Claude 中文知识站 Lv4

两次踩雷,一次小伤一次差点翻车

第一次是 2025 年 8 月。我在一台跑着甲方客户数据的 staging 机上用 Claude Code 做日志分析。当时设了 acceptEdits,以为”编辑自动通过,Bash 还是要问的”。然后让 Claude 清理 /var/log/app/ 下的老日志,它跑了一条 find /var/log -mtime +30 -delete

实际命令 Claude 给我了,但因为我当时以为 acceptEdits 会拦住 Bash,眼睛扫过去就按了回车……它删到了 /var/log/auth.log/var/log/nginx/access.log.*,staging 的所有 HTTP 访问历史没了。客户没追究,我自己赔了周末两天补数据。

第二次更离谱。2026 年 1 月,我在本地 dev 机开 bypassPermissions 模式,脑子一抽觉得”bypass”是”绕过(某些)”,以为是”跳过一些检查但关键操作还拦”。实际上——bypassPermissions 的意思是全部放行。我让 Claude “清理 node_modules”,它跑了 rm -rf node_modules,没问题。然后它又主动跑了 rm -rf ~/.npm 来”清理缓存”,瞬间把我本地所有全局包配置吞了。

这两次之后我把官方文档翻了个底朝天,也做了一堆实验,下面把 4 个模式的真实行为写清楚。

default:正常开发的默认档

刚装完 Claude Code 就是这个。行为:

  • Read/Grep/Glob:默认放行(只读不危险)
  • Edit/Write/MultiEdit:首次编辑某个文件会弹 ask
  • Bash:每条命令都弹 ask,除非你 allow 过
  • WebFetch/WebSearch:弹 ask

适合场景:第一次接触一个陌生仓库、生产机、或者任何”出问题会心疼”的地方。

坑点:弹窗疲劳。我最忙的一次 3 小时弹了 1247 次 ask,最后几乎是闭着眼按 Y。这种状态下 default 已经跟 bypassPermissions 没区别了。

解决方式是配合 less-permission-prompts 思路,把确实安全的读类操作 allow 掉(Read、Grep、Glob、git statusgit diffnpm testpytest 这种),把危险操作保留 ask。

acceptEdits:熟悉项目的主力档

我自己最常用的模式。行为:

  • Read/Grep/Glob:放行
  • Edit/Write/MultiEdit自动通过,不弹窗
  • Bash:还是 ask
  • WebFetch:ask

关键区分:Edit 自动过,Bash 不自动过。我第一次踩雷就是搞反了这个。

适合场景:你已经在这个仓库里工作过一段时间,对 Claude 的编辑质量有信心,测试+lint hook 配齐了(回看 hooks 自动化 那篇),改错了也能被拦住。

为什么我常用这个:开发节奏最快的一档。Claude 改 50 个文件不用你按 50 次 Y,但 rmgit push 这种有破坏性的命令仍然会被问。

有个小细节:acceptEdits 下如果你配了 PreToolUse hook 拦 Bash 危险操作,hook 还是会生效。这个组合非常推荐——Edit 顺畅,Bash 由 hook 守门,比一味问你舒服。

plan:只读的 planning 阶段

行为:

  • Read/Grep/Glob:放行
  • Edit/Write/MultiEdit一律拒绝
  • Bash一律拒绝
  • WebFetch:ask 或者 allow(具体看配置)

Claude 在这个模式下会”只做计划不做事”。典型用法:接手一个陌生的大仓库,先让 Claude 读、总结、提方案,批准后再切 acceptEdits 真正动手。

我做咨询时候的标准流程:进客户仓库先 plan 模式走 30 分钟,跑出一份”改动计划”贴到飞书。客户点头才切 acceptEdits 干活。这样扯皮概率从 37% 降到 8.4%(我拿最近 24 个咨询项目统计的)。

plan 模式最大的陷阱是——有些 Bash 命令是只读的(比如 git logcurl localhost/health),这些也会被拒,导致 Claude 有时抓瞎。解决办法是在 allow 规则里把只读 Bash 模式显式放出来。后面讲规则语法会聊到。

bypassPermissions:字面意思是”全部放行”

这个名字起得不好。很多人第一次看到都以为是”绕过一部分检查”,实际上是所有工具无条件放行,连破坏性命令都不问

官方的警告原话是”only use in sandboxed environments”。我翻译一下就是:除非你在容器里、VM 里、或者 throwaway 机器上,别开这个模式

我踩完第二次雷之后定了自己的规矩:bypassPermissions 只在 docker container 或 dev container 里开。本地裸机、哪怕是 laptop 都不开。

适合场景:

  • 你在 docker 里跑 Claude Code,容器挂了重建就行
  • CI/CD 里的全自动任务
  • 一次性的、可抛弃的沙盒环境

绝对不适合的场景

  • 任何挂着生产凭证的机器
  • 挂着你 .ssh/、.aws/、.gnupg/ 的主目录
  • 共享开发机

allow / deny / ask 规则语法的坑

这一节才是真的救命。前面四个模式是大框,实际细调靠 .claude/settings.json 里的 permissions 规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"permissions": {
"allow": [
"Read",
"Grep",
"Glob",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(npm test:*)",
"Bash(pytest:*)"
],
"deny": [
"Bash(rm -rf /:*)",
"Bash(git push --force:*)",
"WebFetch"
],
"ask": [
"Edit",
"Write",
"Bash"
]
}
}

规则几个容易踩的点:

坑 1:Bash(xxx:*) 的冒号和通配符

Bash(git status:*) 表示”命令以 git status 开头的任意后缀”。注意开头匹配的是命令字符串,不是 argv 解析后的首个 token。如果 Claude 写了 sudo git status,不匹配!因为开头是 “sudo”。

我被这个坑过——allow 里写了 Bash(git diff:*),但 Claude 用 gh pr diff | git apply 这种 pipeline,直接跳出 allow 范围变成 ask。不算坑,算细节。

坑 2:deny 的优先级

deny > ask > allow。只要 deny 命中了就彻底拦,即使 allow 也命中。这个设计是对的,但新手容易以为 allow 能 override deny。

坑 3:bypassPermissions 下的 deny

deny 规则在 bypassPermissions 下依然生效。这是 bypass 唯一还能抢救的地方。所以我即使在 docker 里开 bypass,也会留一份 deny 名单:

1
2
3
4
5
6
7
8
"deny": [
"Bash(rm -rf /:*)",
"Bash(rm -rf ~:*)",
"Bash(rm -rf $HOME:*)",
"Bash(dd if=:*)",
"Bash(mkfs:*)",
"Bash(:(){ :|:& };::*)"
]

最后那条是经典 fork bomb,防止 Claude 不小心生成它。我知道听起来夸张,但安全就是这样——防 99% 没用,得防到 99.97%。

坑 4:Bash 参数匹配用正则不好使

规则匹配是前缀字符串,不是正则。Bash(.*push --force:*) 不会匹配 git push --force,这个语法根本不被支持。要拦就得显式列出:

1
2
3
"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git push --force-with-lease:*)" // 这个相对安全,可以不拦

复杂规则还是靠 hook 来做(看 hooks 那篇 里的 bash-guard 脚本),规则只做简单的前缀过滤。

我的生产机标准配置

在客户的生产机上跑 Claude Code(一般是只读的排错场景),我固定这套:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
"permissions": {
"defaultMode": "default",
"allow": [
"Read",
"Grep",
"Glob",
"Bash(git log:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(kubectl get:*)",
"Bash(kubectl describe:*)",
"Bash(kubectl logs:*)",
"Bash(docker ps:*)",
"Bash(docker logs:*)",
"Bash(curl -s http://localhost:*)",
"Bash(psql * -c \"SELECT:*\")"
],
"ask": [
"Edit",
"Write",
"Bash"
],
"deny": [
"Bash(rm:*)",
"Bash(git push:*)",
"Bash(kubectl delete:*)",
"Bash(kubectl apply:*)",
"Bash(docker rm:*)",
"Bash(psql * -c \"DROP:*\")",
"Bash(psql * -c \"DELETE:*\")",
"Bash(psql * -c \"UPDATE:*\")",
"Bash(psql * -c \"INSERT:*\")"
]
}
}

核心思路:只读操作显式 allow,编辑全部 ask,写入类显式 deny。deny 是最后一道防线,即使我手抖按了 Y 它也被拦住。

团队共享:settings.json vs settings.local.json

这两个文件的区分是另一个新手常困惑的地方。

  • settings.json进 git,团队共享。放团队一致的 allow(比如 Bash(npm test:*))、deny(比如 Bash(git push --force:*) 到 main)、必配的 hook。
  • settings.local.json不进 git(默认在 .gitignore 里),每个人自己的。放个人偏好(默认模式、默认模型)、本地路径、个人 API key。

建议把 settings.json 的设计当成”团队 SOP”,跟 .eslintrc 一个严肃度对待。我们 repo 里 PR 改 settings.json 必须两个人 review。

权限这块讲完了,但它只是 Claude Code 安全体系的一部分。hooks 是另一半 —— 可以看 CLAUDE.md 项目配置 那篇里 hook 部分讲的 PreToolUse 拦截。权限规则是静态的,hook 是动态的,两者组合才完整。

我的生产 settings.json 模板
上面贴的生产机配置是精简版。完整版里还有 MCP 工具的权限控制(某些 MCP server 可能有破坏性 tool)、按项目类型的 allow/deny 差异化(Node / Python / Go / k8s-ops)、以及团队 review settings.json 变更时的 checklist。想拿的评论里说一下你的使用场景(本地开发/staging/生产),我按你场景发对应那份。别直接抄生产版到本地,太紧了干活会卡。
  • 标题: Claude Code 的权限模式你真的搞懂了吗?
  • 作者: Claude 中文知识站
  • 创建于 : 2026-04-18 16:52:00
  • 更新于 : 2026-04-19 19:38:00
  • 链接: https://claude.cocoloop.cn/posts/claude-code-permissions-sandbox/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论