0%

Claude Code 源码解析 (1):Bash 命令执行的安全艺术

Claude Code 源码解析 (1):Bash 命令执行的安全艺术

导读: 这是 Claude Code 20 个功能特性源码解析系列的第 1 篇,深入分析 Bash 命令执行的安全设计艺术。


📋 目录

  1. [问题引入:AI 执行命令的风险](#问题引入 ai-执行命令的风险)
  2. 技术原理:多层安全验证架构
  3. 设计思想:为什么这样设计
  4. 解决方案:完整实现详解
  5. OpenClaw 最佳实践
  6. 总结

问题引入:AI 执行命令的风险

痛点场景

想象这样一个场景:

1
2
3
4
5
6
7
用户:"帮我清理一下 node_modules"

AI 执行:rm -rf node_modules ✅ 正确

但如果 AI 理解错了...

AI 执行:rm -rf / ❌ 灾难!

这不是危言耸听。 AI 助手拥有强大的执行能力,但必须确保安全可控。

核心问题

设计 AI 助手的 Bash 工具时,面临以下挑战:

  1. 能力与安全的平衡

    • AI 需要执行命令来完成工作
    • 但危险命令可能导致系统损坏
  2. 理解与误判的风险

    • AI 可能误解用户意图
    • 路径解析错误可能导致误删
  3. 权限控制的粒度

    • 所有命令同等对待?
    • 还是分级处理?
  4. 用户体验的考量

    • 每次执行都确认?太繁琐
    • 完全不确认?太危险

Claude Code 给出了完整的答案。


技术原理:多层安全验证架构

整体架构

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
用户命令

┌─────────────────────────────────────────┐
│ 第 1 层:危险模式匹配 (1ms) │
│ - 正则表达式匹配已知危险命令 │
│ - 快速拦截 90% 的危险场景 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ 第 2 层:语义分析 (50ms) │
│ - tree-sitter 解析 Bash AST │
│ - 理解命令的真实意图 │
│ - 评估影响范围 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ 第 3 层:路径验证 (5ms) │
│ - 检查路径是否在允许目录内 │
│ - 防止越权访问 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ 第 4 层:权限决策 (可变) │
│ - 规则匹配 → 自动决策 │
│ - AI 分类器 → 智能判断 │
│ - 低置信度 → 用户确认 │
└─────────────────────────────────────────┘

执行/拒绝

第 1 层:危险模式匹配

核心代码:

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
31
32
33
34
35
36
37
38
39
const dangerousPatterns = [
// 文件系统破坏 (critical)
{
pattern: /rm\s+(-[rf]+\s+)*\//,
severity: 'critical',
message: '尝试删除根目录文件'
},
{
pattern: /dd\s+.*\/dev\/(zero|null)/,
severity: 'critical',
message: '尝试写入设备文件'
},
{
pattern: /mkfs\./,
severity: 'critical',
message: '尝试格式化文件系统'
},

// 权限提升 (high)
{
pattern: /chmod\s+(-[Rr]+\s+)*777/,
severity: 'high',
message: '设置过于宽松的权限'
},

// 网络危险 (high)
{
pattern: /curl\s+.*\|\s*(ba)?sh/,
severity: 'high',
message: '下载并执行远程脚本'
},

// 进程注入 (medium)
{
pattern: /eval\s+.*\$\(.*\)/,
severity: 'medium',
message: '动态执行命令'
},
];

设计要点:

  • 覆盖常见危险场景 - rm、dd、mkfs、curl|bash 等
  • 严重级别分类 - critical/high/medium
  • 快速匹配 - 正则表达式,1ms 内完成

第 2 层:语义分析

为什么需要语义分析?

模式匹配有局限性:

1
2
3
4
# 变体命令,模式匹配可能漏掉
r m -rf / # 空格分隔
\\r\\m -rf / # 转义
$(echo rm) -rf / # 命令替换

语义分析使用 tree-sitter 解析 AST:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { parseBash } from 'tree-sitter-bash';

function analyzeSemantics(command: string): SemanticAnalysis {
// 1. 解析为 AST
const ast = parseBash(command);

// 2. 提取命令意图
const intent = extractIntent(ast);

// 3. 分析影响范围
const affectedPaths = extractAffectedPaths(ast);

// 4. 评估风险级别
const riskLevel = evaluateRisk(intent, affectedPaths);

return { intent, riskLevel, affectedPaths };
}

AST 示例:

1
2
3
4
5
6
7
8
9
# 命令
rm -rf /tmp/test

# AST 结构
(Command
name: "rm"
flags: ["-r", "-f"]
arguments: ["/tmp/test"]
)

第 3 层:路径验证

核心逻辑:

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
31
32
33
34
35
function validatePaths(
command: string,
allowedDirs: string[],
cwd: string
): PathValidationResult {
// 1. 提取命令中的所有路径
const extractedPaths = extractPathsFromCommand(command);

// 2. 解析路径 (处理相对路径、符号链接)
const resolvedPaths = extractedPaths.map(path =>
resolvePath(path, cwd)
);

// 3. 检查每个路径是否在允许目录内
const violations: PathViolation[] = [];

for (const resolved of resolvedPaths) {
const isAllowed = allowedDirs.some(dir =>
resolved.startsWith(resolvePath(dir, cwd))
);

if (!isAllowed) {
violations.push({
path: resolved,
reason: 'Path outside allowed directories'
});
}
}

return {
isValid: violations.length === 0,
resolvedPaths,
violations
};
}

路径解析流程:

1
2
3
4
5
6
7
8
9
用户输入:./test.txt

相对路径转绝对:/home/johnzok/project/./test.txt

规范化:/home/johnzok/project/test.txt

解析符号链接:/home/johnzok/project/test.txt

检查是否在允许目录内

第 4 层:权限决策

决策流程:

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
31
async function decidePermission(
command: string,
context: PermissionContext
): Promise<PermissionDecision> {
// 1. 规则匹配 (O(1) 查找)
const ruleMatch = matchRule(command, context.rules);
if (ruleMatch) {
return {
decision: ruleMatch.action,
source: 'rule',
reason: `Matched rule: ${ruleMatch.id}`
};
}

// 2. AI 分类器判断
const classifierResult = await runClassifier(command, context.llm);
if (classifierResult.confidence > 0.85) {
return {
decision: classifierResult.safe ? 'allow' : 'deny',
source: 'classifier',
confidence: classifierResult.confidence,
};
}

// 3. 需要用户确认
return {
decision: 'ask',
source: 'user',
reason: 'Low confidence, requires human judgment'
};
}

权限规则示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rules:
- id: deny-rm-root
tool: bash
pattern: "rm\\s+(-[rf]+\\s+)*/"
action: deny

- id: allow-npm-install
tool: bash
pattern: "npm\\s+install.*"
action: allow
conditions:
agent: devops

- id: ask-git-push
tool: bash
pattern: "git\\s+push.*"
action: ask

设计思想:为什么这样设计

思想 1:多层防御,不依赖单一检查

问题: 如果只有一层检查,漏掉了怎么办?

解决: 多层防御,每层都有不同的检测维度。

1
2
3
模式匹配 → 语义分析 → 路径验证 → 权限决策
↓ ↓ ↓ ↓
已知模式 意图理解 范围限制 智能判断

每层的价值:

层级 优势 局限
模式匹配 快速、准确 只能识别已知模式
语义分析 理解意图 需要额外依赖
路径验证 防止越权 无法判断命令本身
权限决策 智能灵活 可能误判

多层组合 = 95%+ 的危险命令拦截率

思想 2:权限分级,不同风险不同对待

问题: 所有命令都确认,效率太低;都不确认,风险太高。

解决: 根据风险级别分级处理。

风险级别 命令示例 处理方式
critical rm -rf / 直接拒绝
high chmod 777 强制确认 + 备份
medium git push 简化确认
low ls -la 事后通知

设计智慧:

不是一刀切,而是精细化治理。

思想 3:AI 辅助,人类决策

问题: 规则无法覆盖所有场景,AI 判断可能出错。

解决: 规则优先,AI 辅助,人类最终决策。

1
2
3
4
5
明确规则 → 自动决策 (快速)

模糊情况 → AI 判断 (智能)

低置信度 → 人类确认 (安全)

AI 分类器 Prompt:

1
2
3
4
5
6
7
8
9
10
11
12
分析以下命令的安全性:

命令:{command}
执行者:{agent}
当前目录:{cwd}

请判断:
1. 是否有破坏性风险?
2. 是否需要用户确认?
3. 置信度 (0-1)

返回 JSON: {"safe": boolean, "reason": string, "confidence": number}

思想 4:审计日志,可追溯

设计: 所有命令执行都记录日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function logCommandExecution(
command: string,
result: CommandResult,
context: ToolContext
) {
await gateway.log({
type: 'bash_command',
command,
agent: context.agent,
user: context.user,
timestamp: new Date().toISOString(),
result: result.exitCode,
duration: result.duration,
});
}

日志用途:

  • 安全审计
  • 问题排查
  • 行为分析
  • 优化规则

解决方案:完整实现详解

BashTool 核心实现

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
export class BashTool extends Tool {
name = 'bash';
description = 'Execute shell commands with security validation';

async execute(input: BashInput, context: ToolContext): Promise<ToolResult> {
const startTime = Date.now();

try {
// ========== 阶段 1: 安全验证 ==========
const securityResult = await this.securityAnalyzer.analyze(
input.command,
context
);

if (securityResult.shouldBlock) {
return {
success: false,
error: `Command blocked: ${securityResult.reason}`,
errorCode: 'security_violation',
};
}

// ========== 阶段 2: 路径验证 ==========
const pathResult = await this.pathValidator.validate(
input.command,
context.cwd,
context.allowedDirs
);

if (!pathResult.isValid) {
return {
success: false,
error: `Path violation: ${pathResult.violations[0].reason}`,
errorCode: 'path_violation',
};
}

// ========== 阶段 3: 权限检查 ==========
const permissionResult = await this.permissionDecider.decide(
input.command,
context
);

if (permissionResult.decision === 'deny') {
return {
success: false,
error: 'Permission denied',
reason: permissionResult.reason,
};
}

if (permissionResult.decision === 'ask') {
// 请求用户确认
const userDecision = await this.requestUserConfirmation(
input.command,
permissionResult,
context
);

if (!userDecision.approved) {
return {
success: false,
error: 'User denied permission',
};
}
}

// ========== 阶段 4: 命令执行 ==========
const execResult = await this.executor.execute(input, context);

// ========== 阶段 5: 返回结果 ==========
return {
success: execResult.exitCode === 0,
output: execResult.output,
exitCode: execResult.exitCode,
duration: Date.now() - startTime,
};

} catch (error) {
return {
success: false,
error: error.message,
errorCode: 'execution_error',
};
}
}
}

安全分析器实现

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
31
32
33
34
export class BashSecurityAnalyzer {
async analyze(command: string, context: any): Promise<SecurityAnalysis> {
// 1. 模式匹配
const patternMatches = this.checkPatterns(command);

// 2. 语义分析
const semanticAnalysis = await this.analyzeSemantics(command);

// 3. 综合评估
const maxSeverity = this.getMaxSeverity([
...patternMatches.map(m => m.severity),
semanticAnalysis.risk
]);

return {
shouldBlock: maxSeverity === 'critical',
severity: maxSeverity,
reason: this.generateReason(maxSeverity, patternMatches),
sandboxRequired: this.shouldUseSandbox(command, maxSeverity),
};
}

private checkPatterns(command: string): SecurityMatch[] {
const matches: SecurityMatch[] = [];

for (const { pattern, severity, message } of this.dangerousPatterns) {
if (pattern.test(command)) {
matches.push({ pattern: pattern.source, severity, message });
}
}

return matches;
}
}

权限规则配置

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# ~/.openclaw/config/bash-permissions.yaml

rules:
# ========== 拒绝规则 ==========
- id: deny-rm-root
tool: bash
pattern: "rm\\s+(-[rf]+\\s+)*/"
action: deny
reason: "删除根目录文件极其危险"

- id: deny-dd
tool: bash
pattern: "dd\\s+.*"
action: deny
reason: "dd 命令可能破坏数据"

- id: deny-curl-pipe
tool: bash
pattern: "(curl|wget)\\s+[^|]*\\|\\s*(ba)?sh"
action: deny
reason: "下载并执行远程脚本危险"

# ========== 询问规则 ==========
- id: ask-git-push
tool: bash
pattern: "git\\s+push.*"
action: ask

- id: ask-docker-rm
tool: bash
pattern: "docker\\s+rm\\s+.*"
action: ask

# ========== 允许规则 ==========
- id: allow-npm-install
tool: bash
pattern: "npm\\s+install.*"
action: allow
conditions:
agent: devops
cwd: "**/project/**"

- id: allow-ls
tool: bash
pattern: "ls\\s+.*"
action: allow

default:
action: ask
reason: "无匹配规则,需要人工判断"

OpenClaw 最佳实践

实践 1:创建 Bash 工具插件

目录结构:

1
2
3
4
5
6
~/.openclaw/extensions/bash-tool/
├── index.ts
├── tools/
│ └── BashTool.ts
├── config.yaml
└── package.json

插件入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
// index.ts
import { BashTool } from './tools/BashTool';

export const plugin = {
name: 'bash-tool',
version: '1.0.0',

async init(gateway: any) {
const config = loadConfig();
gateway.registerTool('bash', new BashTool(config));
console.log('[bash-tool] Registered');
},
};

实践 2:配置权限规则

步骤:

1
2
3
4
5
6
7
8
9
10
11
# 1. 创建配置文件
mkdir -p ~/.openclaw/config
vim ~/.openclaw/config/bash-permissions.yaml

# 2. 编辑规则 (参考上面的配置示例)

# 3. 重启 Gateway
openclaw gateway restart

# 4. 验证
openclaw gateway status | grep bash-tool

实践 3:与审批系统集成

代码示例:

1
2
3
4
5
6
7
8
9
10
11
// 请求审批
const approval = await context.requestApproval({
type: 'bash_command',
command: input.command,
risk: securityResult.severity,
reason: securityResult.reason,
});

if (!approval.approved) {
return { success: false, error: '用户拒绝' };
}

审批流程:

1
2
3
4
5
6
7
8
9
AI 请求审批

Gateway 通过微信/飞书发送通知

用户回复"同意"或"拒绝"

Gateway 返回审批结果

AI 继续执行或取消

实践 4:审计日志查询

查询命令执行历史:

1
2
3
4
5
6
7
8
# 查看最近执行的命令
openclaw logs bash --limit 50

# 查看特定用户的命令
openclaw logs bash --user john

# 查看失败的命令
openclaw logs bash --status failed

日志格式:

1
2
3
4
5
6
7
8
9
10
11
{
"type": "bash_command",
"command": "npm install",
"agent": "devops",
"user": "john",
"timestamp": "2026-04-03T19:30:00Z",
"result": 0,
"duration": 1523,
"decision": "allow",
"decisionSource": "rule"
}

总结

核心要点

  1. 多层防御 - 不依赖单一安全检查
  2. 权限分级 - 不同风险不同对待
  3. AI 辅助 - 规则 +AI+ 人类决策
  4. 审计日志 - 所有操作可追溯

设计智慧

安全不是功能,而是设计原则。

Claude Code 的 Bash 工具设计告诉我们:

  • 安全优先,宁可过度保护
  • 用户体验,但不能牺牲安全
  • 智能判断,但人类最终决策

下一步

  • 创建 bash-tool 插件
  • 配置权限规则
  • 集成审批系统
  • 添加审计日志

系列文章:

  • [1] Bash 命令执行的安全艺术 (本文)
  • [2] 差异编辑:AI 与人类的沟通语言 (待发布)
  • [3] 文件模式匹配的底层原理 (待发布)

参考资料:

  • Claude Code 源码分析:~/claude-code-analysis/features/01-bash-execution/
  • OpenClaw 文档:https://docs.openclaw.ai

关于作者: John,OpenClaw 平台开发者,专注 AI 助手架构设计与实现。