cc BashTool 源码分析

概述

BashTool 是 Claude Code 中最核心、最复杂的工具之一,12,782 行代码 分布于 18 个源文件。几乎所有需要与操作系统交互的操作------文件读写、git 命令、包管理、构建、测试等------最终都通过 BashTool 执行。

本文档对每个源文件进行逐行级别的功能拆解和架构分析。


文件清单

文件 行数 功能
toolName.ts 2 工具名称常量
BashTool.tsx 1,143 核心实现:Schema 定义、执行逻辑、结果处理
prompt.ts 369 模型使用说明(API description)
bashPermissions.ts 2,621 权限系统:规则匹配、Classifier、模式处理
bashSecurity.ts 2,592 安全检查:命令注入、危险模式检测
bashCommandHelpers.ts 265 复合命令权限拆解
pathValidation.ts 1,303 路径安全验证
readOnlyValidation.ts 1,990 只读命令合法性校验
sedValidation.ts 684 sed 命令参数白名单校验
sedEditParser.ts 322 sed 替换命令解析器
modeValidation.ts 115 权限模式(acceptEdits 等)校验
shouldUseSandbox.ts 153 Sandbox 启用决策
commandSemantics.ts 140 命令退出码语义解释
destructiveCommandWarning.ts 102 危险命令 UI 警告
commentLabel.ts 13 Bash 注释标签提取
UI.tsx 184 工具使用和进度 UI 渲染
BashToolResultMessage.tsx 190 工具结果 UI 渲染
utils.ts 223 工具函数集合

1. toolName.ts------工具名称常量

typescript 复制代码
export const BASH_TOOL_NAME = 'Bash'

作用:将工具名称定义为常量,避免各处硬编码字符串。

设计要点

  • 单独放在一个文件中是为了打破循环依赖prompt.tsBashTool.tsx 相互引用)
  • 工具在 Anthropic API 中注册为 name: 'Bash',模型通过名称调用

2. BashTool.tsx------核心实现(1,143 行)

这是 BashTool 的主文件,包含:Schema 定义、buildTool() 配置、命令执行引擎。

2.1 导入阶段(1-52)

依赖的关键模块:

  • zod:定义输入输出 schema
  • Shell.ts / ShellCommand.ts:底层 shell 执行引擎
  • bashPermissions.ts:权限检查
  • bashSecurity.ts:安全验证
  • SandboxManager:沙箱隔离
  • fileHistory.ts:文件历史追踪
  • toolResultStorage.ts:大结果持久化

2.2 常量定义(54-78)

typescript 复制代码
const PROGRESS_THRESHOLD_MS = 2000          // 2秒后显示进度
const ASSISTANT_BLOCKING_BUDGET_MS = 15_000 // 助理模式 15秒后自动后台化

定义四个命令分类集合:

typescript 复制代码
BASH_SEARCH_COMMANDS     // 搜索类命令:find, grep, rg, ag, ack, locate...
BASH_READ_COMMANDS       // 读取类命令:cat, head, tail, less, more, jq, awk...
BASH_LIST_COMMANDS       // 目录列表:ls, tree, du
BASH_SEMANTIC_NEUTRAL_COMMANDS  // 语义中立:echo, printf, true, false...
BASH_SILENT_COMMANDS     // 静默命令:mv, cp, rm, mkdir, chmod, touch, ln, cd...

设计意图:这些分类用于:

  1. isSearchOrReadBashCommand():判断命令是否可折叠显示(UI 层面)
  2. isSilentBashCommand():判断命令是否预期无输出(显示"Done"替代"(No output)")

2.3 isSearchOrReadBashCommand(95-172)

typescript 复制代码
export function isSearchOrReadBashCommand(command: string): {
  isSearch: boolean;
  isRead: boolean;
  isList: boolean;
}

逐行逻辑

  1. splitCommandWithOperators(command)&&, ||, |, ; 分割复合命令
  2. 跳过重定向目标(> file 后的 file)
  3. 跳过语义中立命令(echo 等)
  4. 检查每个非中立段是否是搜索/读取/列表命令
  5. 所有非中立段都必须是搜索/读取命令才返回 true

安全设计 :如果任何一段不是搜索/读取命令,整体就不折叠。防止 grep foo && rm -rf / 被错误标记为只读。

2.4 输入 Schema 定义(227-263)

使用 lazySchema() 延迟创建 Zod schema(避免模块加载时的循环依赖):

typescript 复制代码
const fullInputSchema = lazySchema(() => z.strictObject({
  command: z.string().describe('The command to execute'),
  timeout: semanticNumber(z.number().optional()).describe(...),
  description: z.string().optional().describe(...),
  run_in_background: semanticBoolean(z.boolean().optional()).describe(...),
  dangerouslyDisableSandbox: semanticBoolean(z.boolean().optional()).describe(...),
  _simulatedSedEdit: z.object({...}).optional().describe(...),
}));

关键设计

  • _simulatedSedEdit内部字段 ,通过 omit() 从模型可见的 schema 中移除(253-259)。防止模型绕过权限检查直接写文件。
  • strictObject 确保模型不会传入未定义的参数
  • semanticNumber / semanticBoolean 是包装器,帮助模型更准确地生成数值/布尔值

2.5 输出 Schema(279-294)

typescript 复制代码
const outputSchema = lazySchema(() => z.object({
  stdout: z.string(),               // 标准输出
  stderr: z.string(),               // 标准错误
  interrupted: z.boolean(),         // 是否被中断
  isImage: z.boolean().optional(),  // 是否图片输出
  backgroundTaskId: z.string().optional(),  // 后台任务 ID
  persistedOutputPath: z.string().optional(), // 大输出持久化路径
  ...
}));

2.6 buildTool 配置(420-825)

buildTool() 是工具定义的核心。以下是关键方法的逐行分析:

prompt()(431-433)

typescript 复制代码
async prompt() { return getSimplePrompt(); }

委托给 prompt.ts 生成模型看到的使用说明。

isConcurrencySafe()(434-436)

typescript 复制代码
isConcurrencySafe(input) { return this.isReadOnly(input); }

只读命令可并发:读命令(ls, cat, grep 等)可以并行执行;写命令串行执行。

isReadOnly()(437-441)

调用 checkReadOnlyConstraints() 做深层语义分析:

  • 检查命令是否只读(git log、cat 等)
  • 检查是否有目录变更(cd)
  • 检查参数是否安全(如 git push --force 不被视为只读)

preparePermissionMatcher()(445-468)

为 Hook 系统准备命令模式匹配器

  1. 使用 parseForSecurity(command) 解析命令 AST
  2. 提取所有子命令的 argv(去除前导 env var 赋值)
  3. 返回匹配函数,支持前缀匹配和通配符匹配

例如 FOO=bar git push 匹配 Bash(git *) 规则。

validateInput()(524-538)

typescript 复制代码
async validateInput(input): Promise<ValidationResult> {
  // 检测阻塞式 sleep 模式
  const sleepPattern = detectBlockedSleepPattern(input.command);
  if (sleepPattern !== null) {
    return {
      result: false,
      message: `Blocked: ${sleepPattern}. Run blocking commands in the background...`
    };
  }
  return { result: true };
}

防呆设计 :检测 sleep 5sleep 10 && check 等模式,阻止模型用 sleep 做轮询,引导使用 run_in_background 或 Monitor 工具。

核心 call() 方法(624-820)

这是 BashTool 的执行入口,逐行分析关键路径:

阶段 1:模拟 sed 编辑(627-629)

typescript 复制代码
if (input._simulatedSedEdit) {
  return applySedEdit(input._simulatedSedEdit, toolUseContext, parentMessage);
}

如果输入包含 _simulatedSedEdit(由权限对话框用户预览后设置),直接写文件而非执行 sed。

阶段 2:创建命令执行器(646-657)

typescript 复制代码
const commandGenerator = runShellCommand({ input, abortController, ... });

创建异步生成器 runShellCommand(),这是实际执行命令并发逐步进的核心函数。

阶段 3:消费进度事件(661-679)

bash 复制代码
do {
  generatorResult = await commandGenerator.next();
  if (!generatorResult.done && onProgress) {
    onProgress({ toolUseID, data: { type: 'bash_progress', output, ... } });
  }
} while (!generatorResult.done);

实时消费 shell 输出的进度,通过 onProgress 回调传递给 UI。

阶段 4:结果处理(682-719)

  • trackGitOperations():追踪 git 操作事件
  • interpretCommandResult():解释退出码(grep 返回 1 不是错误)
  • .git/index.lock 检测:记录冲突事件
  • resetCwdIfOutsideProject():如果 cd 到了项目外,自动重置
  • SandboxManager.annotateStderrWithSandboxFailures():标注沙箱违规
  • 错误时抛出 ShellError,包含完整 stdout/stderr

阶段 5:大输出持久化 (732-753) 当输出超过阈值时,复制到 tool-results 目录,模型收到预览和文件路径而非完整内容。超过 64MB 自动截断。

阶段 6:Claude Code Hints 协议(780-784)

typescript 复制代码
const extracted = extractClaudeCodeHints(strippedStdout, input.command);

CLIs/SDKs 可以通过 stderr 输出 <claude-code-hint /> 标签(gate on CLAUDECODE=1),实现零 token 的侧信道通信。扫描后从 stdout 剥离,模型永远看不到标签。

阶段 7:图片输出检测(785-802)

typescript 复制代码
let isImage = isImageOutput(strippedStdout);
if (isImage) {
  const resized = await resizeShellImageOutput(strippedStdout, ...);
}

检测 base64 图片 data URI,自动缩放超大图片(CC-304)。

2.7 runShellCommand 生成器(826-1036)

这是异步生成器函数,逐行执行命令并分段产生进度。

关键机制:进度信号(870-875)

typescript 复制代码
let resolveProgress: (() => null) | null = null;
function createProgressSignal(): Promise<null> {
  return new Promise<null>(resolve => { resolveProgress = () => resolve(null); });
}

每次 exec()onProgress 回调被调用时,调用 resolveProgress() 唤醒生成器 yield 一段新输出。

自动后台化(877-898)

  • 超时自动后台(967-971):命令超时后不杀死,转为后台继续运行
  • 助理模式自动后台(976-983):Kairos 模式下主 agent 的阻塞命令超过 15 秒自动后台化,保持 agent 响应性
  • 显式后台 (989-999):run_in_background: true 时立即后台

exec() 调用(881-898):

typescript 复制代码
const shellCommand = await exec(command, abortController.signal, 'bash', {
  timeout: timeoutMs,
  onProgress(lastLines, allLines, totalLines, totalBytes, isIncomplete) { ... },
  preventCwdChanges,
  shouldUseSandbox: shouldUseSandbox(input),
  shouldAutoBackground,
});

底层 shell 执行,支持:

  • 超时控制
  • 进度回调
  • CWD 变更预防(子 agent 不允许 cd)
  • 沙箱执行
  • 自动后台化

2.8 mapToolResultToToolResultBlockParam()(555-623)

将工具执行结果转为 API 的 tool_result block:

typescript 复制代码
// 结构化内容(MCP)
if (structuredContent?.length > 0) return { content: structuredContent };

// 图片输出
if (isImage) { const block = buildImageToolResult(stdout, toolUseID); ... }

// 大输出持久化
if (persistedOutputPath) {
  processedStdout = buildLargeToolResultMessage({ ... });
}

// 后台任务信息
if (backgroundTaskId) { backgroundInfo = `Command running in background...`; }

// 最终结果
return {
  tool_use_id: toolUseID,
  content: [processedStdout, errorMessage, backgroundInfo].filter(Boolean).join('\n'),
  is_error: interrupted,
};

3. prompt.ts------模型使用说明(369 行)

getSimplePrompt() 返回的字符串作为 API 的 description 字段发送给模型,让模型理解 BashTool 的使用方式。

3.1 内容结构

bash 复制代码
Executes a given bash command and returns its output.
[空行]
The working directory persists between commands, but shell state does not.
[空行]
IMPORTANT: Avoid using this tool to run cat/head/tail/sed/awk/echo commands...
[空行]
- File search: Use GlobTool (NOT find or ls)
- Content search: Use GrepTool (NOT grep or rg)
- Read files: Use FileReadTool (NOT cat/head/tail)
- Edit files: Use FileEditTool (NOT sed/awk)
- Write files: Use FileWriteTool (NOT echo >/cat <<EOF)
- Communication: Output text directly (NOT echo/printf)
[空行]
# Instructions
 - 命令编写规范
 - 路径引用
 - CWD 管理
 - 超时参数说明
 - 后台任务用法
 - git 安全协议
 - sleep 命令规范
[可选] Sandbox 配置
[可选] git 操作说明

3.2 关键设计

  • 优先使用专用工具:反复强调优先用 Read/Edit/Write/Glob/Grep,Bash 是兜底方案
  • Sandbox 说明动态生成getSimpleSandboxSection()):根据当前 sandbox 配置动态生成限制说明
  • git 安全协议动态包含getCommitAndPRInstructions()):根据用户设置决定是否包含详细 git 操作指南
  • 约 300-400 行 的详细说明,但实际上 引导模型在大多数场景下不要直接使用 Bash

4. bashPermissions.ts------权限系统(2,621 行)

这是 BashTool 中最庞大的文件,实现完整的命令权限评估流水线:分层规则检查、Bash Classifier、复合命令拆解。

4.1 核心函数:bashToolHasPermission()

typescript 复制代码
export async function bashToolHasPermission(
  input: z.infer<typeof BashTool.inputSchema>,
  context: ToolUseContext,
): Promise<PermissionResult>

调用链 (从 BashTool.tsx:539-541checkPermissions() 进入):

scss 复制代码
checkPermissions()
  └─ bashToolHasPermission()
       ├─ 步骤 1: checkPermissionMode()
       │   └─ acceptEdits 模式 → 自动允许文件系统命令(mkdir, touch, rm 等)
       │
       ├─ 步骤 2: bashCommandIsSafe() [安全检查]
       │   ├─ validateEmpty()             → 空命令自动允许
       │   ├─ validateIncompleteCommands() → 不完整命令(tab 开头、以操作符开头)→ ask
       │   ├─ validateJqSystemFunction()  → jq system() 函数 → deny
       │   ├─ validateJqFileArguments()   → jq -f 参数 → ask
       │   ├─ validateObfuscatedFlags()   → 混淆标志(缩短的 hex、octal)→ ask
       │   ├─ validateShellMetacharacters() → shell 元字符 → ask
       │   ├─ validateDangerousVariables()  → 危险环境变量(IFS, LD_PRELOAD 等)→ deny
       │   ├─ validateNewlines()          → 命令内嵌换行 → ask
       │   ├─ validateDangerousPatterns() → 命令替换/重定向模式 → ask/deny
       │   ├─ validateIfsInjection()      → IFS 注入 → ask
       │   ├─ validateGitCommitSubstitution() → git commit 内容替换 → ask
       │   ├─ validateProcEnvironAccess() → /proc/environ 读取 → ask/deny
       │   ├─ validateMalformedTokens()   → 畸形 token → ask
       │   ├─ validateBackslashWhitespace() → 反斜杠空白 → ask
       │   ├─ validateBraceExpansion()    → 花括号展开({/,var/log} 等)→ ask
       │   ├─ validateControlCharacters() → 控制字符 → deny/ask
       │   ├─ validateUnicodeWhitespace() → Unicode 空白(换行等)→ ask/deny
       │   ├─ validateMidWordHash()       → 中间单词注释 → ask
       │   ├─ validateZshDangerousCommands() → Zsh 危险命令(zmodload 等)→ deny
       │   ├─ validateBackslashEscapedOperators() → 反斜杠转义操作符 → ask
       │   ├─ validateCommentQuoteDesync()    → 注释/引号失步 → ask
       │   ├─ validateQuotedNewline()        → 引号内换行 → ask
       │   └─ 全部 passthrough → 安全
       │
       ├─ 步骤 3: 如果有 heredoc 且命令是复合的,拆段递归检查
       │   └─ segmentedCommandPermissionResult()
       │
       ├─ 步骤 4: checkPathConstraints()
       │   ├─ cd 命令 → 目标地址必须在允许的工作目录内
       │   ├─ rm/mv → 目标路径安全校验(禁止删除根目录等)
       │   ├─ 输出重定向 → 目标路径必须在工作目录内
       │   ├─ sed 编辑 → sedValidation 白名单校验
       │   └─ 文件读取 → 路径合法性和目录约束
       │
       ├─ 步骤 5: 权限规则匹配(用户配置的 alwaysAllow / alwaysDeny / alwaysAsk)
       │   ├─ 精确匹配 → 直接判定
       │   ├─ 前缀匹配 → 按规则处理
       │   ├─ 通配符匹配 → 按规则处理
       │   └─ 无规则 → 走 Classifier
       │
       ├─ 步骤 6: 复合命令(&&, ||, |, ;)
       │   ├─ cd + git 跨段 → ask(防止 bare repo fsmonitor 绕过)
       │   ├─ 多 cd → ask
       │   └─ 每段递归调用 bashToolHasPermission() 检查
       │
       ├─ 步骤 7: Bash Classifier(AI 驱动分类器)
       │   ├─ allow 规则 → 自动允许
       │   ├─ deny 规则 → 自动拒绝
       │   ├─ ask 规则 → 提示用户
       │   └─ 无匹配 → 兜底走用户提示
       │
       └─ 返回 behavior: 'allow' | 'deny' | 'ask'

4.2 命令前缀提取(161-264)

getSimpleCommandPrefix() 从命令中提取稳定的"命令+子命令"前缀用于规则建议:

typescript 复制代码
'git commit -m "fix"'  → 'git commit'
'npm run build'        → 'npm run'
'ls -la'               → null        // 子命令是 flag
'cat file.txt'         → null        // 子命令是文件名

安全设计:跳过非安全环境变量(MY_VAR=val npm run build → null),防止恶意规则建议。

4.3 BARE_SHELL_PREFIXES(196-226)

禁止为以下命令创建前缀建议规则:

typescript 复制代码
bash, zsh, sh, fish,            // shell 本身
env, xargs, sudo, doas, pkexec,  // 包装器/提权
nice, nohup, timeout, time,      // 命令包装器

原因 :如果创建了 Bash(sudo:*) 规则,后续 sudo rm -rf / 直接自动允许。

4.4 Classifier 集成

条件编译(仅 feature('BASH_CLASSIFIER') 启用时生效):

  • 调用 classifyBashCommand() 对命令做 AI 分类
  • 支持 Allow / Ask / Deny 三种分类结果
  • 仅在 auto 模式下启用
  • 分类器描述动态构建,包含当前工作目录上下文

5. bashSecurity.ts------安全检查引擎(2,592 行)

实现 bashCommandIsSafe() 函数,通过约 20 道独立的安全检查验证命令是否安全。

5.1 ValidationContext(103-117)

typescript 复制代码
type ValidationContext = {
  originalCommand: string        // 原始命令
  baseCommand: string            // base 命令名
  unquotedContent: string        // 去双引号后的内容
  fullyUnquotedContent: string   // 去所有引号后的内容
  fullyUnquotedPreStrip: string  // 去引号但保留重定向(给花括号检查用)
  unquotedKeepQuoteChars: string // 保留引号字符(给注释检测用)
  treeSitter?: TreeSitterAnalysis | null // tree-sitter AST(可选)
}

5.2 安全检查列表

检查函数 检查编号 检测内容 默认行为
validateEmpty - 空命令 allow
validateIncompleteCommands 1 Tab 开头、以操作符开头、以 flag 开头 ask
validateJqSystemFunction 2 jq 的 system() / exec() 调用 ask
validateJqFileArguments 3 jq -f 从文件加载 ask
validateObfuscatedFlags 4 混淆标志(0x2D 等) ask
validateShellMetacharacters 5 shell 元字符反引号 ask
validateDangerousVariables 6 IFS=, PATH=, LD_PRELOAD= deny
validateNewlines 7 命令内嵌换行符 ask
validateDangerousPatterns 8-10 命令替换、输入/输出重定向 ask/deny
validateIfsInjection 11 IFS 字段分隔符注入 ask
validateGitCommitSubstitution 12 git commit 内容替换 ask
validateProcEnvironAccess 13 /proc/self/environ 读取 ask
validateMalformedTokens 14 shell-quote 畸形 token deny
validateBackslashWhitespace 15 反斜杠转义的空白字符 ask
validateBraceExpansion 16 花括号展开(泄露路径) ask
validateControlCharacters 17 控制字符 deny
validateUnicodeWhitespace 18 Unicode 空白字符 ask
validateMidWordHash 19 词中注释符号 ask
validateZshDangerousCommands 20 zsh 危险内建命令 deny
validateBackslashEscapedOperators 21 反斜杠转义的操作符 ask
validateCommentQuoteDesync 22 注释/引号失配 ask
validateQuotedNewline 23 引号内换行 ask

5.3 关键安全检查细节

validateDangerousVariables :检查 IFS=PATH=LD_PRELOAD=LD_LIBRARY_PATH=BASH_ENV=ENV=SHELLOPTS= 等危险环境变量。这些变量可以被用来劫持命令执行。

validateDangerousPatterns:检测:

  • 命令替换模式:$(), ${}, $[], 反引号
  • 进程替换:<(), >(), =()
  • Zsh 特定扩展:~[glob], (e:code:), }always{
  • PowerShell 注释语法 <#(防御性编程)

validateZshDangerousCommands :禁止 zmodload(加载模块的网关)、emulate -c(eval 等价物)、以及 zsh/system、zsh/net/tcp、zsh/zpty 等危险模块的内建命令。

validateBraceExpansion(CC-519):检测花括号展开中的路径泄露。

  • {/,var/log} → 暴露根目录下 /var/log
  • 对去重定向后内容跳验证

6. bashCommandHelpers.ts------复合命令助手(265 行)

6.1 segmentedCommandPermissionResult()

处理复合命令(cmd1 && cmd2)的分段权限检查:

typescript 复制代码
async function segmentedCommandPermissionResult(
  input, segments, bashToolHasPermissionFn, checkers,
): Promise<PermissionResult>

逐行逻辑

  1. 多 cd 检测 (31-47):多个 cd 命令需要用户确认
  2. cd + git 跨段攻击检测 (55-81):检测 cd 和 git 在不同管道段中(cd sub && echo | git status),防止 bare repo fsmonitor 绕过
  3. 每段递归权限检查 (84-96):每段调 bashToolHasPermissionFn()
  4. 拒绝聚合(99-100+):任一被 deny → 整体 deny

7. pathValidation.ts------路径安全验证(1,303 行)

7.1 PATH_EXTRACTORS 注册表(约 50 行)

typescript 复制代码
export const PATH_EXTRACTORS: Record<PathCommand, PathExtractor> = {
  cd:     (args) => extractLastPath(args, { allowMissing: true }),
  ls:     (args) => extractLastPath(args),
  rm:     (args) => rmPathExtractor(args),
  mv:     (args) => extractPaths(args, { minArgs: 2 }),
  sed:    (args) => extractSedPath(args),
  git:    (args) => extractGitPath(args),
  docker: (args) => extractDockerPath(args),
  // ...等30+个命令
};

为每个命令类型注册特定的路径提取器。

7.2 checkDangerousRemovalPaths()(70-80)

rm -rf /, rm -rf ~, rm -rf . 等做硬性检查,即使是 allowlist 规则也不允许。

7.3 checkPathConstraints()(约 400 行)

核心入口,对每个命令类型执行不同的路径验证策略:

bash 复制代码
全路径验证(cat, head, tail, grep 等对文件读的命令)
  └─ validatePath(filePath, cwd, allowedDirs, FileOperationType.Read)

写路径验证(重定向 >, >>, sed -i)
  └─ validatePath(filePath, cwd, allowedDirs, FileOperationType.Write)

删除路径验证(rm, rmdir)
  └─ 额外 checkDangerousRemovalPaths()

cd 路径验证
  └─ cd 目标必须在 allowedDirs 内

git 路径验证
  └─ 结合 git 子命令的参数解析

8. readOnlyValidation.ts------只读校验(1,990 行)

8.1 命令安全标志白名单

逐命令定义允许的安全 flag:

typescript 复制代码
const FD_SAFE_FLAGS = {
  '-h': 'none', '--help': 'none',
  '-H': 'none', '--hidden': 'none',
  '-i': 'none', '--ignore-case': 'none',
  // -x/--exec → 故意排除(执行任意命令)
};

类似的白名单有:

  • GIT_READ_ONLY_COMMANDS:包含 log, diff, status, show, branch, blame
  • GH_READ_ONLY_COMMANDS:GitHub CLI 只读命令
  • DOCKER_READ_ONLY_COMMANDS:Docker 只读命令
  • PYRIGHT_READ_ONLY_COMMANDS:Pyright 只读命令
  • RIPGREP_READ_ONLY_COMMANDS:ripgrep 只读命令
  • EXTERNAL_READONLY_COMMANDS:外部工具只读白名单

8.2 checkReadOnlyConstraints()

typescript 复制代码
export function checkReadOnlyConstraints(
  input: z.infer<typeof BashTool.inputSchema>,
  compoundCommandHasCd: boolean,
): PermissionResult

判断命令是否只读的核心规则

  1. 有 cd → 不是只读(会改变状态)
  2. 没有 cd → 检查所有子命令的 flag 是否在白名单内
  3. 有输出重定向 → 不是只读
  4. sed -i 编辑 → 不是只读
  5. 复合命令中任一子命令不是只读 → 整体不是只读

9. sedValidation.ts------sed 安全检查(684 行)

9.1 sed 命令白名单

typescript 复制代码
const SED_EXPRESSION_ALLOWLIST = [
  's',           // 替换
  'y',           // 字符转换
  'd',           // 删除行
  's/[^/]*//g',  // 全局空替换
  // ...更多白名单模式
];

9.2 安全检查函数

  • isLinePrintingCommand() (44-80):检测 sed -n '1p;2p;3p' 等纯打印命令,允许文件参数
  • sedCommandIsAllowedByAllowlist():检查 sed 表达式是否在白名单内
  • sedWriteCommandIsReadOnly():判断 sed 写命令是否可视为只读

安全原则:只允许白名单内的 sed 表达式,其他的 sed -i 编辑走权限提示。


10. sedEditParser.ts------sed 命令解析器(322 行)

10.1 parseSedEditCommand()

sed -i 's/old/new/g' file.txt 解析为结构化信息:

typescript 复制代码
type SedEditInfo = {
  filePath: string       // file.txt
  pattern: string        // old
  replacement: string    // new
  flags: string          // g
  extendedRegex: boolean // 是否 -E 标志
}

10.2 applySedSubstitution()

将 sed 替换语义应用到 JavaScript 字符串:

  • BRE→ERE 转换:将基本正则表达式转义为 JS 兼容的 ERE 格式
  • 使用空字节占位符处理转义序列,避免冲突
  • 支持 &$&(完整匹配)和 \n → 换行符 的转换

安全设计 :使用 crypto.randomBytes(8) 生成盐来防止占位符注入。


11. modeValidation.ts------权限模式校验(115 行)

11.1 checkPermissionMode()

typescript 复制代码
export function checkPermissionMode(
  input, toolPermissionContext,
): PermissionResult
  • acceptEdits 模式 :自动允许 mkdir, touch, rm, rmdir, mv, cp, sed 等文件系统操作
  • bypassPermissions / dontAsk 模式:跳过本校验,交给上层权限系统
  • 返回 'passthrough' 表示不干预,继续走后续检查链

12. shouldUseSandbox.ts------沙箱决策(153 行)

12.1 shouldUseSandbox()

export function shouldUseSandbox(input: Partial<SandboxInput>): boolean

决策逻辑

  1. 沙箱全局关闭 → false
  2. dangerouslyDisableSandbox=true 且策略允许绕过 → false
  3. 无命令 → false
  4. 命令在被排除列表中(用户配置的 sandbox.excludedCommands)→ false
  5. 否则 → true

12.2 containsExcludedCommand()

检查命令是否在排除列表中:

  • 动态配置 (ant-only):tengu_sandbox_disabled_commands feature flag
  • 用户配置settings.jsonsandbox.excludedCommands
  • 支持精确匹配、前缀匹配、通配符匹配
  • 自动剥离环境变量前缀和安全包装器后再匹配

13. commandSemantics.ts------退出码语义(140 行)

13.1 命令退出码语义表

命令 语义 解释
grep 0=匹配, 1=无匹配, 2+=错误 1 不是错误
rg 同上 ripgrep 同 grep
find 0=成功, 1=部分成功, 2+=错误 1 不是错误
diff 0=相同, 1=不同, 2+=错误 1 不是错误
test/[ 0=true, 1=false, 2+=错误 1 不是错误
其他 0=成功, 非0=错误 默认语义

13.2 interpretCommandResult()

typescript 复制代码
export function interpretCommandResult(command, exitCode, stdout, stderr) {
  const semantic = getCommandSemantic(command);
  return semantic(exitCode, stdout, stderr);
}

设计意图 :防止模型对 grep 返回 1(无匹配)等正常情况产生误判。在 BashTool.tsx:690 中,当 interpretationResult.isError===true 且非中断时,才抛出 ShellError


14. destructiveCommandWarning.ts------破坏性命令警告(102 行)

14.1 检测模式

typescript 复制代码
const DESTRUCTIVE_PATTERNS = [
  git reset --hard,         // 丢弃未提交更改
  git push --force,         // 覆盖远程历史
  git clean -f,             // 永久删除未跟踪文件
  git checkout -- .         // 丢弃工作区更改
  rm -rf, rm -r, rm -f,     // 递归强制删除
  DROP TABLE/DATABASE,      // 数据库删除
  kubectl delete,           // K8s 资源删除
  terraform destroy,        // Terraform 资源销毁
  ...
];

纯 UI 提示,不影响权限逻辑。


15. commentLabel.ts------注释标签(13 行)

typescript 复制代码
export function extractBashCommentLabel(command: string): string | undefined {
  const firstLine = ...trim();
  if (!firstLine.startsWith('#') || firstLine.startsWith('#!')) return undefined;
  return firstLine.replace(/^#+\s*/, '') || undefined;
}

提取 bash 命令首行的注释作为 UI 标签。例如:

bash 复制代码
# Install dependencies
npm install

→ UI 显示 "Install dependencies" 而非原始命令。


16. UI.tsx------UI 渲染(184 行)

16.1 导出的渲染函数

typescript 复制代码
export function renderToolUseMessage(input, options): ReactNode        // 命令输入显示
export function renderToolUseProgressMessage(msgs, options): ReactNode // 进度显示
export function renderToolUseQueuedMessage(): ReactNode                // 排队状态
export function renderToolResultMessage(result, options): ReactNode    // 结果渲染
export function renderToolUseErrorMessage(result, options): ReactNode  // 错误渲染
export function BackgroundHint(props): ReactNode                       // 后台任务提示

16.2 BackgroundHint 组件

监听 ctrl+b 快捷键,将所有前台运行中的命令转为后台。


17. BashToolResultMessage.tsx------结果 UI 渲染(190 行)

React 组件,处理:

  • 图片输出 :直接显示 [Image data detected and sent to Claude]
  • Sandbox 违规 :从 stderr 提取 <sandbox_violations> 标签并剥离显示
  • CWD 重置警告 :检测 Shell cwd was reset to <path> 并以警告色显示
  • 无输出 :按优先级显示 running in background / returnCodeInterpretation / Done / (No output)
  • 超时显示:展示命令的超时时间

18. utils.ts------工具函数(223 行)

18.1 函数清单

typescript 复制代码
stripEmptyLines(content)          // 去除首尾空白行
isImageOutput(content)            // 检测 base64 图片 data URI
parseDataUri(s)                   // 解析 data URI → { mediaType, data }
buildImageToolResult(stdout, id)  // 构建图片 tool result
resizeShellImageOutput(stdout, ...) // 缩放超大图片(CC-304)
formatOutput(content)             // 输出格式化(截断 + 计数)
stdErrAppendShellResetMessage(stderr) // 添加 "Shell cwd was reset" 消息
resetCwdIfOutsideProject(ctx)     // 如果 cd 到项目外则重置
createContentSummary(blocks)      // 创建结构化内容摘要

18.2 resizeShellImageOutput() 的安全设计

  • 最大文件限制:20MB(MAX_IMAGE_FILE_SIZE
  • 自动缩放超大/高 DPI 图片(matplotlib dpi=300 场景)
  • 解析失败时返回 null,调用者负责降级

完整调用链(从模型到执行)

css 复制代码
模型决定调用 Bash("npm run build")
    ↓
BashTool.tsx: checkPermissions()
    ↓
bashPermissions.ts: bashToolHasPermission()
    ├─ modeValidation.ts: checkPermissionMode()       → 模式特定处理
    ├─ bashSecurity.ts: bashCommandIsSafe()            → 20道安全检查
    ├─ pathValidation.ts: checkPathConstraints()       → 路径合法验证
    ├─ 权限规则匹配(用户配置)                            → 规则引擎
    ├─ bashCommandHelpers.ts: segmentedCheck()         → 复合命令拆解
    └─ bashClassifier(AI 分类器,auto模式)              → 自动判定
    ↓
返回 PermissionResult(allow / deny / ask)
    ↓
用户批准(或自动允许)
    ↓
BashTool.tsx: call() 执行
    ├─ runShellCommand() → exec()
    │   ├─ shouldUseSandbox.ts → 确定沙箱策略
    │   ├─ ShellCommand.execute() → 实际 shell 调用
    │   ├─ 流式输出收集(EndTruncatingAccumulator)
    │   ├─ 超时 → 自动后台化 / 超时错误
    │   └─ 完成 → ExecResult
    ├─ commandSemantics.ts: interpretCommandResult()  → 解释退出码
    ├─ 大输出 → 持久化 + 预览
    ├─ claude-code-hint 提取
    └─ 图片 → 检测 + 缩放
    ↓
mapToolResultToToolResultBlockParam()
    ├─ 结构内容 → 直接返回
    ├─ 图片 → buildImageToolResult()
    ├─ 大输出 → buildLargeToolResultMessage()
    └─ 常规 → { content: stdout + stderr + backgroundInfo }
    ↓
返回 tool_result → 模型处理结果

关键安全设计模式

  1. 分层防御:权限规则 → 安全扫描 → 路径验证 → Classifier → 用户确认
  2. 默认拒绝 :任何安全检查失败都 denyask,绝不静默允许
  3. 内部字段隐藏_simulatedSedEdit 从模型 schema 中移除,防止绕过
  4. 命令拆分验证:复合命令的每段单独走完整权限检查链
  5. 跨段攻击检测:cd + git 在不同 pipe 段中的组合攻击检测
  6. 只读合并的保守策略:所有段都必须是只读命令才允许并发执行
  7. 不存在客户端自动重试:失败返回给模型自主决定
相关推荐
Harry Lei3 小时前
先问对问题,再找答案——hl-research 的完整探索记录
claude·skill
Keine Zeit4 小时前
解决Claude Code 安装报错 `Unable to connect to Anthropic services`
claude
视频砖家4 小时前
了解何时使用 CLAUDE.md、Skills、subagents、hooks、MCP 和 plugins
claude·skills
Resistance丶未来6 小时前
管控用量,降本增效,MAI Gateway:助力企业搭建 Tokens 统一管理体系
人工智能·大模型·api·claude·ai安全·魔芋ai·maigateway
Aqoo16 小时前
给AI智能体装红灯:Recuse Signal让LLM学会主动退出
openai·claude
Python私教18 小时前
给AI代理选大脑:别只盯着『谁最强』,这6个维度才决定上限
agent·ai编程·claude
码哥字节19 小时前
我把 Matt Pocock 的 18 个 Skill 全用了一遍,才发现自己一直在瞎用 AI
ai编程·claude·vibecoding
星浩AI20 小时前
Agnes AI 免费 API 接入指南:文本、生图、生视频,一套接口全免费
llm·api·claude