Skip to main content

权限系统:信任是设计出来的

用 Auto 模式跑 Claude Code 的时候,每次操作自动放行这事儿看起来像「魔法」。但表象背后其实跑着两个 AI——一个执行你的任务,一个独立审查操作是否安全。权限系统就是这一整套审查机制的工程实现。

三种权限模式

本节源码:src/utils/permissions/PermissionMode.ts

源码里把权限模式分得很清楚:

模式行为适用场景
default / interactive每个危险操作都弹窗确认日常使用,安全优先
autoML 分类器自动判断是否安全熟练用户,效率优先
yolo / bypassPermissions跳过所有确认非交互式脚本,完全信任

大多数人关心的是 auto 模式——它怎么决定一个操作放不放?

Auto 模式的四层审查流水线

本节源码:src/utils/permissions/yoloClassifier.ts

Auto 模式不是简单让 AI 自己判断。它是一个四层流水线,每层像一道门禁,越往后越严格:

  1. 规则匹配:检查用户预设的权限规则。比如在 settings.json 里写了「允许所有 git 命令」,那任何 git 操作直接放行,不进后面。
  2. 低风险跳过:某些操作天然是低风险的(读文件、搜索代码),根本不值得为它浪费一次 AI 调用。
  3. 白名单放行:只读工具(Read、Grep、Glob 等)默认在白名单上,它们的设计就是「不改任何东西」。
  4. ML 分类器判断:前三层都没拦住的操作,调用一个独立的 Claude Sonnet 做安全分类。这个分类器和主 AI 是分离的——有自己的 system prompt(auto_mode_system_prompt.txt),有自己训练的目标函数,专门用来判断操作风险等级。

为什么这么设计? 性能和成本。每次调用 ML 分类器都是一次额外的 API 调用,有延迟也有费用。四层流水线确保只有真正需要判断的操作才走到第四层——绝大多数日常操作在前三层就处理了,用户几乎感受不到安全检查的存在。

YOLO 分类器是怎么实现的

本节源码:src/utils/permissions/yoloClassifier.ts

第四层的 ML 分类器在源码里叫 yoloClassifier,实现文件是 src/utils/permissions/yoloClassifier.ts。它会给每个操作输出三种判断之一:

判断含义后续动作
Allow操作安全直接放行
Soft Deny有风险,需用户确认弹窗让用户决定
Hard Deny危险操作直接拦截

两阶段判断——源码里最精巧的设计

分类器实际上用了两阶段判断

Stage 1:快速判断(64 token 预算)

先用极小的 token 预算做第一轮筛选。输出格式很简单:yes / no。如果判断是「安全」直接放行,不进入 Stage 2

为什么只给 64 token?因为大多数操作明显安全(读文件、搜索代码),不需要深度推理。一个简单 yes/no 64 token 足够。

Stage 2:深度推理(4096 token 预算)

只有 Stage 1 判断为「可能危险」的操作才进入。这次给 4096 token,要求输出详细的推理过程:

input_schema: {
properties: {
explanation: {
description: 'What this command does (1-2 sentences)',
},
reasoning: {
description: `Why YOU are running this command. Start with "I", ...`,
},
risk: {
description: 'What could go wrong, under 15 words',
},
riskLevel: {
enum: ['LOW', 'MEDIUM', 'HIGH'],
description: 'LOW (safe dev workflows), MEDIUM (recoverable changes), HIGH (dangerous/irreversible)',
},
},
}

两阶段的好处是成本优化:安全操作在 Stage 1 就放行了(花极少 token),只有可疑操作才需要 Stage 2 的深度分析。统计下来大部分操作不走 Stage 2。

让分类器判断更靠谱的几个细节

温度设为 0——两个阶段的 temperature 都设为 0,相同输入产生相同输出。安全判断不需要创造性,需要确定性

CLAUDE.md 被注入分类器上下文——分类器不只看操作本身,还看用户在 CLAUDE.md 里写的配置。如果 CLAUDE.md 写了「允许对 /tmp 的任何操作」,分类器会把这条规则当作用户意图来参考。这意味着 CLAUDE.md 不仅影响主 AI 的行为,也影响安全判断——用户配置的安全边界会延伸到分类器

内外部版本用不同的权限模板——源码加载两套:外部用户默认 permissions_external.txt,内部员工默认 permissions_anthropic.txt。外部用户的自定义规则替换默认规则,内部员工的追加到默认规则之上。

对话历史转录压缩——分类器看到的对话历史不是完整消息,而是一种紧凑格式(比如 FileEdit 只发送文件路径但省略文件内容),减少 token 消耗。判断意图不需要原文细节。

熔断机制:分类器也会误判

本节源码:src/utils/permissions/denialTracking.ts

源码里有一个精巧的 denialTracking.ts 模块:

export const DENIAL_LIMITS = {
maxConsecutive: 3,
maxTotal: 20,
} as const

export function shouldFallbackToPrompting(
state: DenialTrackingState
): boolean {
return (
state.consecutiveDenials >= DENIAL_LIMITS.maxConsecutive ||
state.totalDenials >= DENIAL_LIMITS.maxTotal
)
}

规则很简洁:连续被拒 3 次,或者累计被拒 20 次,Auto 模式自动降级为手动确认模式

为什么要熔断?想象一个场景——主 AI 在执行一个合理任务,分类器反复把它误判为危险操作。没有熔断的话,AI 会一直重试然后被拒,陷入死循环。熔断机制让系统在「分类器异常」时优雅降级,而不是卡死。

成功执行会重置连续拒绝计数器(但不重置总计数器)——偶尔被拒不会触发熔断,但频繁被拒一定会触发。

受保护文件和路径防御

本节源码:src/utils/permissions/pathValidation.ts

源码中定义了一系列受保护的文件,任何操作都不允许修改

  • .gitconfig —— Git 全局配置
  • .bashrc / .zshrc —— Shell 配置
  • .claude.json —— Claude Code 自身的配置
  • .mcp.json —— MCP 服务器配置

这些文件之所以受保护,是因为修改它们可能导致持久的、跨会话的危害。比如 AI 修改了 .bashrc 加入一条恶意命令,那么用户每次打开终端都会执行那条命令。

路径穿越防御也很完善。源码检查了 unicode 编码、大小写变换、反斜杠注入等常见路径穿越手法。防止 AI(或通过 prompt 注入攻击的黑客)用 ../../.bashrc 这样的路径绕过保护。

危险权限的自动剥离

本节源码:src/utils/permissions/permissionSetup.tsstripDangerousPermissionsForAutoMode 在此文件);清单在 src/utils/permissions/dangerousPatterns.ts

日常使用中你可能配置过这些宽泛规则:

Bash(*) # 允许所有 bash 命令
Bash(python:*) # 允许所有 python 命令

手动确认模式下这些规则没什么问题——你每次都能看到具体操作。但在 Auto 模式下,这些规则会让大量操作绕过分类器(第一层规则匹配直接放行)。

源码的解决方案很优雅:进入 Auto 模式时自动剥离这些危险的宽泛规则,暂存起来;退出 Auto 模式时恢复。用户配置没有被永久修改,只是在 Auto 模式期间临时屏蔽:

export function stripDangerousPermissionsForAutoMode(
context: ToolPermissionContext
): ToolPermissionContext {
// 收集所有 alwaysAllowRules
const rules: PermissionRule[] = []
for (const [source, ruleStrings] of Object.entries(context.alwaysAllowRules)) {
// ...解析每条规则
const dangerousPermissions =
findDangerousClassifierPermissions(rules, [])
// 剥离危险规则,同时暂存到 strippedDangerousRules
...
}
return { ...context, dangerousPermissions, strippedDangerousRules: ... }
}

哪些规则被判定为「危险」?

模式说明
Bash()无参数 = 允许所有命令
Bash(*)通配符,同上
Bash(python:*)允许所有 python 命令(python 可执行任意代码)
Bash(node:*), Bash(ruby:*)其他脚本解释器的同类规则

为什么不直接拒绝进入 Auto 模式让用户手动清理? Anthropic 的设计哲学是:它不信任用户的手动权限配置在自动化环境中仍然安全。你在手动使用时觉得「管它什么命令都让我看一眼」,但 Auto 模式下没人值守,宽泛规则会直接变成安全漏洞。系统在模式切换时自动适配安全等级,而不是把这个判断留给用户。

风险评估器:把判断做成人话

本节源码:src/utils/permissions/permissionExplainer.ts

分类器判断「拒绝」时,不只是说「不允许」。它会调用另一个独立的 LLM 调用生成人类可读的解释——告诉用户这个操作可能是什么、能做什么、可能出什么问题。

三档风险分级

riskLevel: {
enum: ['LOW', 'MEDIUM', 'HIGH'],
}
等级含义典型操作
LOWsafe dev workflows运行测试、格式化代码
MEDIUMrecoverable changes安装包、修改配置文件
HIGHdangerous/irreversible删除文件、force push

上下文感知的判断

系统把最近的对话上下文(最后 3 条 AI 消息,最多 1000 字符)注入评估请求。风险评估器不只看操作本身,还能理解操作的上下文。同样是 rm 命令,在「清理临时文件」的上下文里是 LOW 风险,在没有任何上下文时可能被评估为 HIGH。

reasoning 字段还要求以 I 开头——「I'm running this to check test results」——迫使模型从第一人称解释意图,而不是干巴巴的技术描述。用户读起来更自然,也更容易判断是否真的就是要这么做。

risk 字段强制限制在 15 个词以内——逼迫模型把风险浓缩到最核心的部分,而不是长篇大论列所有可能性。用户在弹窗里需要快速判断,不是阅读论文。

用户可以通过配置 permissionExplainerEnabled 关闭这个功能,但默认开启。Anthropic 的设计哲学很清晰:如果你要拒绝或质疑用户的操作,至少给一个人能理解的理由,而且理由要是人话,不是技术错误码

Zig 层的 DRM:编译级安全

本节源码:src/services/api/claude.ts

社区在源码泄露后挖出了一个出人意料的安全层——Claude Code 的 API 请求中带一个 cch=00000 占位符。请求离开进程之前,Bun 底层的 Zig 编译代码会把这 5 个零替换成计算出的加密哈希。服务端验证这个哈希,确认请求来自真正的 Claude Code 二进制文件。

为什么用 Zig 而不是 JavaScript? JavaScript 可以在运行时被 monkey-patch——任何函数都可以被覆盖、任何 HTTP 请求都可以被拦截。但 Zig 编译进 Bun 的代码无法在运行时修改,除非重新编译整个运行时。这本质上是 API 调用的 DRM,实现在 HTTP 传输层。

普通用户触碰不到这一层,但它是关键防线——防止有人破解客户端后篡改请求、伪造调用、重放流量。

Bash 安全的 23 项检查

本节源码:src/tools/BashTool/bashSecurity.ts(注意:不在 src/utils/permissions/,而是和 BashTool 同目录)

bashSecurity.ts 里编了 23 项编号的安全检查,覆盖很多非显而易见的攻击面

  • Zsh 等号展开防御(=curl 在 Zsh 中会展开成执行 curl 命令)
  • Unicode 零宽空格注入
  • IFS 空字节注入
  • 18 个被阻止的 Zsh 内建命令
  • HackerOne 审查中发现的畸形 token 绕过

每一项背后都有一个真实的攻击场景。权限系统不是理论上的安全设计,是跟真实漏洞交手过的实战产物。

这个系统能教我们什么

本节源码:src/utils/permissions/yoloClassifier.ts

安全系统的投入应该和核心功能一样多。

Claude Code 的权限系统涉及多个文件、数千行代码、独立的 ML 分类器、Zig 层 DRM、23 项 Bash 安全检查、熔断机制、路径防御——不是一个周末写的 feature,而是一个持续投入的核心系统

几条朴素的结论:

  • 多层防御,逐层过滤——不要把所有判断压在一层上。简单判断用规则(白名单、规则匹配),复杂判断才上 ML 分类器。四层流水线让 99% 的操作快速通过,少数可疑操作才走到贵的那层。
  • 安全和效率不是对立的——四层流水线让大多数操作在前三层放行,用户几乎感受不到安全检查。只有真正可疑的操作才会触发第四层 AI 判断,那时候多等两秒完全可接受。好的安全系统是隐形的
  • 设计降级路径——熔断机制的存在说明 Anthropic 承认分类器不完美。与追求完美的分类器,不如设计好出错时的降级行为。连续被拒 3 次就降级为手动确认,简单、可靠、用户不会卡住。
  • 保护持久化文件——AI 修改临时文件影响有限,但修改 Shell 配置、系统配置这类持久化文件的影响是跨会话、跨项目的。对这类文件的保护级别应该远高于普通文件。
  • 权限策略要随模式切换而调整——用户在手动模式下设置的宽泛权限,在自动模式下可能是漏洞。好的系统会在模式切换时自动适配安全等级,而不是把这个判断留给用户。
  • 给用户做判断的信息,而不只是按钮——一个「允许/拒绝」的弹窗如果不告诉用户操作做什么、为什么做、可能出什么问题,用户要么盲点允许要么盲目拒绝。风险评估器生成的结构化解释,把权限确认从「打扰」变成了「informed decision」。