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 && B或A ; B这样的指令,系统会调用splitCommand将其拆分为独立的子命令。安全性要求:每一个子项都必须独立通过校验。哪怕十个子命令里只有一个被 Deny,整条指令都会被拦截。 - 重定向审计(Operator Check):扫描
>、>>等重定向符号。如果模型尝试将输出重定向到一个未授权的文件(即使主命令只是echo),系统也会识别为“写操作”并实施拦截。 - 路径深度校验(Path Validation):扫描参数中的所有路径引用。即使路径隐藏在复杂的参数深处,也会被提取并与
allowedDirectories进行越界对比。
2. 语义风险扫描(Semantics Check)
利用 src/utils/bash/ast.ts 识别危险模式:
- 敏感工具拦截:系统会特别识别
sed -i、git config、crontab等可能修改系统关键配置的命令。 - 交互式 Shell 禁止:Claude 被严格禁止开启交互式 Shell(如直接运行
bash或ssh不带参数)。这是为了防止在交互环境下,系统的审计链完全断链。
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。
接下来看什么
- 如果你想了解在沙箱环境下如何简化这些规则,请看 Sandbox Auto-Allow:基于物理隔离的自动授权。
- 如果你关心路径校验的具体算法,请看 权限与沙箱:双层防御体系。
- 如果你想了解如何在命令行中管理这些复杂的
Bash(*)规则,请看 权限管理与规则配置:/permissions 详解。
源码锚点
- claude-code-opensource/src/tools/BashTool/bashPermissions.ts:1303 —
splitCommand:复合命令的深度拆解逻辑实现。
📄 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,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- claude-code-opensource/src/utils/bash/ast.ts — 核心:基于 AST 的 Shell 语义风险分析。
📄 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'1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- claude-code-opensource/src/utils/permissions/shellRuleMatching.ts — 统配符匹配与前缀过滤的算法逻辑。
📄 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
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16