代码语言
选择后全站课程跟随

权限系统

3,100 字 · 预计阅读 9 分钟

前情提要

  • 权限规则写在 settings.json 里,分三类:allow 直接放行,ask 先问你,deny 直接拒绝
json
{
  "permissions": {
    "allow": ["Bash(npm run test *)"],
    "ask":   ["Bash(git push *)"],
    "deny":  ["Read(./.env)", "WebFetch"]
  }
}
  • 有六种权限模式:手动(default)、自动接受编辑(acceptEdits)、计划(plan)、自动(auto)、不问就拒(dontAsk)、全自主(bypassPermissions)
  • 几条使用经验:allow 绕不开 deny;规则按文字匹配;全自主模式下,少数高危操作照样会问你

第3节的 mini-cc 有一个很粗的权限判定:只读的放行,其余都问。

这一节去看 Claude Code 的权限判定到底长什么样。读完你会明白一句话:规则是 Claude Code 执行的,不是模型。 也会明白它的边界在哪:写了 deny,也不一定就万无一失。

代码在 src/utils/permissions/permissions.ts。


一、一张写死的流程表

权限判定先检查拒绝规则,再决定询问或放行

权限判定是七道关里的第 ④ 关。把那个函数(hasPermissionsToUseToolInner())简化一下,就是一张按顺序走的表:

1a. 整个工具在 deny 里            → 拒绝
1b. 整个工具在 ask 里             → 问你
1c. 工具自己检查参数               (比如 Bash 会把命令拆开,逐条去匹配你的规则)
1d.   工具说 deny                → 拒绝
1f.   命中了你写的带内容的 ask 规则  → 问你(全自主模式也照问)
1g.   要改敏感路径                 → 问你(全自主模式也照问)
2a. 当前是全自主模式               → 放行
2b. 整个工具在 allow 里           → 放行
3.  都没命中                      → 问你

从头看到尾,你会发现一件事:没有一步是去问模型的意见。

模型在提示词里被告诉「不要 push」,那是它「想不想」的事。这张表管的是「能不能」:不管模型怎么想,走到哪一步就是哪个结果。

权限判定是一张写死的表,模型说了不算。


二、deny 为什么最先查

表的第一行就是 deny。命中了直接返回「拒绝」,后面的 allow 根本轮不到。

所以有这条经验:allow 绕不开 deny。 你在 allow 里写了 Bash(*),又在 deny 里写了 Bash(rm -rf *),后者照样生效。

这个「拒绝优先」的原则,在 Claude Code 里到处都是:

地方 怎么体现 在哪节
权限规则 deny 最先查 本节
Hook Hook 说「放行」,也绕不开 deny 的口子 第9节
插件 插件不能通过设置给自己加权限 第10节

为什么这么设计?因为两种出错的代价不一样:该放的被拦了,你多点一次「允许」;不该放的被放了,可能是一次删库。

拒绝永远比放行优先,因为放错的代价远比拦错大。


三、六种模式,各改了哪一格

六种权限模式,在代码里其实都是同一张表。它们只是在不同的位置,把「问你」换成了别的处理:

模式 在代码里做了什么
手动 default 什么都不改,走到第 3 步就弹窗问你
自动接受编辑 acceptEdits 文件工具自己检查时(1c),对工作目录里的编辑直接放行
计划 plan 不自动放行改动,再配合一条提醒让模型只读(第12节)
自动 auto 本来要问你的,先交给一个分类器(另一个模型)判断
不问就拒 dontAsk 本来要问你的,直接拒绝
全自主 bypassPermissions 在 2a 直接放行,但 1a~1g 照常生效

最后一行值得多看一眼。全自主模式听起来像「什么都不管」,其实它是在 2a 才放行的。排在它前面的 deny 规则、你写的 ask 规则、敏感路径检查,一个都没跳过。

本文是会员内容

全文 3,100 字,已试读约 29%。 开通专业版可阅读全文,并解锁全部课程与后续更新。

开通专业版

支持 7 天无理由退款 · 早鸟期前 500 名自动升级永久卡