Skip to content
源码分析手册

permission-rules:细粒度权限规则的匹配引擎

Claude Code 能够安全地在你的代码库运行 rm -rf,靠的不是 LLM 的自觉,而是一套严密的权限规则匹配引擎。无论处于哪种模式,任何工具执行前都要经过这道安检。

核心概念

本质上,它是一个 基于层级优先级的过滤链(Filter Chain)

它将“工具名称”和“工具参数”作为输入,匹配预设的规则集(Ruleset),最终产出 allow(放行)、deny(拒绝)或 ask(弹窗请示)三种裁决结果。

代码里的真实逻辑

核心逻辑封装在 claude-code-opensource/src/utils/permissions/permissions.ts

1. 规则的数据结构

每一条规则(PermissionRule)由以下部分组成(见 PermissionRule.ts):

  • toolName:匹配的工具(如 bashls)。
  • ruleContent:匹配模式(Pattern),通常是 Glob 或正则(如 /usr/bin/*)。
  • ruleBehaviorallow | deny | ask
  • source:来源(优先级关键),包括 managed(企业管控)、cliArg(命令行)、projectSettingsuserSettings

2. 匹配流程(Matching Logic)

当工具请求执行时,hasPermissionsToUseToolInner 函数会按优先级扫描:

  1. Deny First:如果命中任何 deny 规则,直接拒绝。
  2. Ask Second:如果命中 ask 规则,无论该工具是否属于默认放行范畴,都强制弹窗。
  3. Bypass Mode 特权:如果用户启动了 --bypass-permissions,除了某些涉及核心安全的 safetyCheck(如修改 .claude 配置本身),其余一律 allow
  4. Allow Last:命中 allow 规则则放行。
  5. Default Action:如果没有任何规则命中,则降级到 PermissionMode 的默认行为(如 default 模式下为 ask)。

3. 参数级匹配(Argument Pattern Matching)

对于 bash 这种危险工具,引擎会深入参数。源码调用 shellRuleMatching.ts 中的 matchWildcardPattern。它支持通配符匹配,比如允许执行 npm test 但拦截 npm publish。这在 src/utils/permissions/filesystem.ts 中针对文件路径读写权限有大量实现。

实战注意事项

  1. 来源优先级(Source Precedence)Managed Settings > CLI Args > Project Settings > User Settings。 如果你的公司管理员禁用了 bash,你在命令行加 -y 也是没用的。
  2. Bash 的“虚假”匹配: Claude Code 并不是简单地字符串匹配 Bash 命令。它会先通过 extractOutputRedirections 解析命令,识别出它是在读文件还是写文件,然后去调对应的文件权限规则。
  3. Auto 模式下的隐式放行: 在 auto 模式下,如果规则没有显式定义,引擎会求助于 classifierDecision.ts(基于 LLM 的分类器)来决定是否自动放行。

接下来看什么

  • 想看 Bash 具体的命令过滤逻辑?查看 claude-code-opensource/src/utils/permissions/shellRuleMatching.ts
  • 想看自动模式的 AI 判定?查看 claude-code-opensource/src/utils/permissions/classifierDecision.ts

源码锚点

📄 src/utils/permissions/permissions.ts — `hasPermissionsToUseToolInner` 核心判定逻辑。L1148-1168 of 1487
typescript
    toolPermissionResult?.behavior === 'ask' &&
    toolPermissionResult.decisionReason?.type === 'safetyCheck'
  ) {
    return toolPermissionResult
  }

  // No rule-based objection
  return null
}

async function hasPermissionsToUseToolInner(
  tool: Tool,
  input: { [key: string]: unknown },
  context: ToolUseContext,
): Promise<PermissionDecision> {
  if (context.abortController.signal.aborted) {
    throw new AbortError()
  }

  let appState = context.getAppState()
📄 src/utils/permissions/PermissionRule.ts — 权限规则 Schema 定义。L25-41 of 41
typescript
export const permissionBehaviorSchema = lazySchema(() =>
  z.enum(['allow', 'deny', 'ask']),
)

/**
 * PermissionRuleValue is the content of a permission rule.
 * @param toolName - The name of the tool this rule applies to
 * @param ruleContent - The optional content of the rule.
 *   Each tool may implement custom handling in `checkPermissions()`
 */
export const permissionRuleValueSchema = lazySchema(() =>
  z.object({
    toolName: z.string(),
    ruleContent: z.string().optional(),
  }),
)
📄 src/utils/permissions/filesystem.ts — 针对文件 IO 的分级权限检查。L1330-1350 of 1778
typescript
      message: safetyCheck.message,
      suggestions: safetySuggestions,
      decisionReason: {
        type: 'safetyCheck',
        reason: safetyCheck.message,
        classifierApprovable: safetyCheck.classifierApprovable,
      },
    }
  }

  // 2. Check for ask rules - check both the original path and resolved symlink path
  for (const pathToCheck of pathsToCheck) {
    const askRule = matchingRuleForInput(
      pathToCheck,
      toolPermissionContext,
      'edit',
      'ask',
    )
    if (askRule) {
      return {
        behavior: 'ask',

基于 Claude Code v2.1.88 开源快照的深度分析