Skip to content
源码分析手册

Bash 权限规则:超越正则的 AST 语义审计

Bash 是 Claude Code 中最具威力也最危险的工具。为了防止模型通过复杂的 Shell 技巧(如管道、重定向、复合命令)绕过基础的安全检查,系统在 BashTool 中实现了一套深度语义扫描机制。

它解决了什么问题

Bash 权限规则本质上是 基于抽象语法树(AST)的命令审计协议

  • 它不是什么:它不是简单的字符串正则匹配(Regex Matching)。如果只是简单的正则,模型只需使用 echo "rm -rf /" | sh 就能轻松骗过拦截器。
  • 它的核心理念:在命令执行前,先将其彻底拆解成逻辑结构,识别出哪些是主命令、哪些是参数、哪些是重定向目标,并对每一个组件应用独立的权限规则。这是一种“结构化安全”的思想。

实现细节

Bash 命令的权限裁决遵循一套严密的“拆解-扫描-匹配”逻辑链,主要由 claude-code-opensource/src/tools/BashTool/bashPermissions.ts 驱动:

1. AST 解析与三层过滤

利用 src/utils/bash/parser.ts 尝试将命令解析为语法树:

  • 复合命令拆解(Splitting):对于 A && BA ; B 这样的指令,系统会调用 splitCommand 将其拆分为独立的子命令。安全性要求:每一个子项都必须独立通过校验。哪怕十个子命令里只有一个被 Deny,整条指令都会被拦截。
  • 重定向审计(Operator Check):扫描 >>> 等重定向符号。如果模型尝试将输出重定向到一个未授权的文件(即使主命令只是 echo),系统也会识别为“写操作”并实施拦截。
  • 路径深度校验(Path Validation):扫描参数中的所有路径引用。即使路径隐藏在复杂的参数深处,也会被提取并与 allowedDirectories 进行越界对比。

2. 语义风险扫描(Semantics Check)

利用 src/utils/bash/ast.ts 识别危险模式:

  • 敏感工具拦截:系统会特别识别 sed -igit configcrontab 等可能修改系统关键配置的命令。
  • 交互式 Shell 禁止:Claude 被严格禁止开启交互式 Shell(如直接运行 bashssh 不带参数)。这是为了防止在交互环境下,系统的审计链完全断链。

3. 多维度匹配算法(Rule Matching)

shellRuleMatching.ts 中实现了两种颗粒度的规则:

  • 精确匹配(Exact Match):如 Bash(npm test)
  • 通配符/前缀匹配(Prefix Match):如 Bash(ls:*)。 这一逻辑允许用户一次性放行某一类良性的操作流,而无需针对每个细小的参数差异重复点击授权。

实战注意事项

  • 拒绝优先权(Deny Precedence):子命令的 Deny 规则拥有最高优先级。在复合命令中,任何一部分的违规都会导致整体的失败。
  • 管道(Pipe)的黑盒属性:虽然 AST 扫描能识别大部分重定向,但极其复杂的管道嵌套流(如三层以上的 Pipe)仍然是语义分析的难点。对于这类难以确定的结构,系统倾向于将其路由到最严格的 Ask(弹窗询问)。
  • 本地别名(Alias)失效:权限系统识别的是“原始敲下的命令”。如果用户在本地终端定义了复杂的 Alias,系统可能无法准确预测别名展开后的语义风险,因此会回退到最基础的安全检查。
  • 非原生环境风险:在非标准的 Shell 环境(如 Windows 上的某些模拟器)中,AST 解析可能会失败,此时系统会进入“保守模式”,将所有包含特殊字符的命令标记为 Ask

接下来看什么

源码锚点

📄 src/tools/BashTool/bashPermissions.ts — `splitCommand`:复合命令的深度拆解逻辑实现。L89-118 of 2622
typescript
const splitCommand = splitCommand_DEPRECATED

// Env-var assignment prefix (VAR=value). Shared across three while-loops that
// skip safe env vars before extracting the command name.
const ENV_VAR_ASSIGN_RE = /^[A-Za-z_]\w*=/

// CC-643: On complex compound commands, splitCommand_DEPRECATED can produce a
// very large subcommands array (possible exponential growth; #21405's ReDoS fix
// may have been incomplete). Each subcommand then runs tree-sitter parse +
// ~20 validators + logEvent (bashSecurity.ts), and with memoized metadata the
// resulting microtask chain starves the event loop — REPL freeze at 100% CPU,
// strace showed /proc/self/stat reads at ~127Hz with no epoll_wait. Fifty is
// generous: legitimate user commands don't split that wide. Above the cap we
// fall back to 'ask' (safe default — we can't prove safety, so we prompt).
export const MAX_SUBCOMMANDS_FOR_SECURITY_CHECK = 50

// GH#11380: Cap the number of per-subcommand rules suggested for compound
// commands. Beyond this, the "Yes, and don't ask again for X, Y, Z…" label
// degrades to "similar commands" anyway, and saving 10+ rules from one prompt
// is more likely noise than intent. Users chaining this many write commands
// in one && list are rare; they can always approve once and add rules manually.
export const MAX_SUGGESTED_RULES_FOR_COMPOUND = 5

/**
 * [ANT-ONLY] Log classifier evaluation results for analysis.
 * This helps us understand which classifier rules are being evaluated
 * and how the classifier is deciding on commands.
 */
function logClassifierResultForAnts(
  command: string,
📄 src/utils/bash/ast.ts — 核心:基于 AST 的 Shell 语义风险分析。L2-21 of 2680
typescript
 * AST-based bash command analysis using tree-sitter.
 *
 * This module replaces the shell-quote + hand-rolled char-walker approach in
 * bashSecurity.ts / commands.ts. Instead of detecting parser differentials
 * one-by-one, we parse with tree-sitter-bash and walk the tree with an
 * EXPLICIT allowlist of node types. Any node type not in the allowlist causes
 * the entire command to be classified as 'too-complex', which means it goes
 * through the normal permission prompt flow.
 *
 * The key design property is FAIL-CLOSED: we never interpret structure we
 * don't understand. If tree-sitter produces a node we haven't explicitly
 * allowlisted, we refuse to extract argv and the caller must ask the user.
 *
 * This is NOT a sandbox. It does not prevent dangerous commands from running.
 * It answers exactly one question: "Can we produce a trustworthy argv[] for
 * each simple command in this string?" If yes, downstream code can match
 * argv[0] against permission rules and flag allowlists. If no, ask the user.
 */

import { SHELL_KEYWORDS } from './bashParser.js'
📄 src/utils/permissions/shellRuleMatching.ts — 统配符匹配与前缀过滤的算法逻辑。L14-29 of 229
typescript
const ESCAPED_STAR_PLACEHOLDER = '\x00ESCAPED_STAR\x00'
const ESCAPED_BACKSLASH_PLACEHOLDER = '\x00ESCAPED_BACKSLASH\x00'
const ESCAPED_STAR_PLACEHOLDER_RE = new RegExp(ESCAPED_STAR_PLACEHOLDER, 'g')
const ESCAPED_BACKSLASH_PLACEHOLDER_RE = new RegExp(
  ESCAPED_BACKSLASH_PLACEHOLDER,
  'g',
)

/**
 * Parsed permission rule discriminated union.
 */
export type ShellPermissionRule =
  | {
      type: 'exact'
      command: string
    }

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