Claude Code 源码解析 (1):Bash 命令执行的安全艺术
架构师点评:Bash 安全不是“禁止执行命令”,而是让 AI 在可授权、可回滚、可审计的边界内执行命令。企业引入 Claude Code 或自研 Coding Agent 时,命令权限模型必须早于自动化能力上线。
导读: 这是 Claude Code 20 个功能特性源码解析系列的第 1 篇,深入分析 Bash 命令执行的安全设计艺术。
📋 目录
- [问题引入:AI 执行命令的风险](#问题引入 ai-执行命令的风险)
- 技术原理:多层安全验证架构
- 设计思想:为什么这样设计
- 解决方案:完整实现详解
- OpenClaw 最佳实践
- 总结
问题引入:AI 执行命令的风险
痛点场景
想象这样一个场景:
1 2 3 4 5 6 7
| 用户:"帮我清理一下 node_modules"
AI 执行:rm -rf node_modules ✅ 正确
但如果 AI 理解错了...
AI 执行:rm -rf / ❌ 灾难!
|
这不是危言耸听。 AI 助手拥有强大的执行能力,但必须确保安全可控。
核心问题
设计 AI 助手的 Bash 工具时,面临以下挑战:
能力与安全的平衡
- AI 需要执行命令来完成工作
- 但危险命令可能导致系统损坏
理解与误判的风险
权限控制的粒度
用户体验的考量
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 = [ { pattern: /rm\s+(-[rf]+\s+)*\//, severity: 'critical', message: '尝试删除根目录文件' }, { pattern: /dd\s+.*\/dev\/(zero|null)/, severity: 'critical', message: '尝试写入设备文件' }, { pattern: /mkfs\./, severity: 'critical', message: '尝试格式化文件系统' }, { pattern: /chmod\s+(-[Rr]+\s+)*777/, severity: 'high', message: '设置过于宽松的权限' }, { pattern: /curl\s+.*\|\s*(ba)?sh/, severity: 'high', message: '下载并执行远程脚本' }, { 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 { const ast = parseBash(command); const intent = extractIntent(ast); const affectedPaths = extractAffectedPaths(ast); const riskLevel = evaluateRisk(intent, affectedPaths); return { intent, riskLevel, affectedPaths }; }
|
AST 示例:
1 2 3 4 5 6 7 8 9
| rm -rf /tmp/test
(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 { const extractedPaths = extractPathsFromCommand(command); const resolvedPaths = extractedPaths.map(path => resolvePath(path, cwd) ); 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 ↓ 相对路径转绝对:~/workspace/project/./test.txt ↓ 规范化:~/workspace/project/test.txt ↓ 解析符号链接:~/workspace/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> { const ruleMatch = matchRule(command, context.rules); if (ruleMatch) { return { decision: ruleMatch.action, source: 'rule', reason: `Matched rule: ${ruleMatch.id}` }; } const classifierResult = await runClassifier(command, context.llm); if (classifierResult.confidence > 0.85) { return { decision: classifierResult.safe ? 'allow' : 'deny', source: 'classifier', confidence: classifierResult.confidence, }; } 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, }); }
|
日志用途:
解决方案:完整实现详解
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 { const securityResult = await this.securityAnalyzer.analyze( input.command, context ); if (securityResult.shouldBlock) { return { success: false, error: `Command blocked: ${securityResult.reason}`, errorCode: 'security_violation', }; } 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', }; } 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', }; } } const execResult = await this.executor.execute(input, context); 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> { const patternMatches = this.checkPatterns(command); const semanticAnalysis = await this.analyzeSemantics(command); 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
|
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
| 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
| mkdir -p ~/.openclaw/config vim ~/.openclaw/config/bash-permissions.yaml
openclaw gateway restart
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" }
|
总结
核心要点
- 多层防御 - 不依赖单一安全检查
- 权限分级 - 不同风险不同对待
- AI 辅助 - 规则 +AI+ 人类决策
- 审计日志 - 所有操作可追溯
设计智慧
安全不是功能,而是设计原则。
Claude Code 的 Bash 工具设计告诉我们:
- 安全优先,宁可过度保护
- 用户体验,但不能牺牲安全
- 智能判断,但人类最终决策
下一步
系列文章:
- [1] Bash 命令执行的安全艺术 (本文)
- [2] 差异编辑:AI 与人类的沟通语言 (待发布)
- [3] 文件模式匹配的底层原理 (待发布)
- …
参考资料:
关于作者: John,OpenClaw 平台开发者,专注 AI 助手架构设计与实现。
企业落地建议
这篇源码解析对应的是企业 AI Coding 平台里的基础治理能力,建议落地时重点关注:
- 把命令分为只读、可修改、高风险、禁止四类:并明确审批策略。
- 对
rm、curl|sh、凭据读取、网络扫描等命令做静态规则和运行时拦截。:对 rm、curl|sh、凭据读取、网络扫描等命令做静态规则和运行时拦截。
- 保留完整命令审计日志:至少记录发起人、上下文、工作目录、退出码和输出摘要。
- 把 Bash 权限策略纳入团队 AI Coding 规范:而不是依赖个人自觉。
相关入口:
Claude Code 系列的价值,不只是学习一个工具,而是拆解企业级 Coding Agent 应该具备的工程能力:上下文、权限、任务、工具、安全和可观测。