cc BashTool 源码分析

5 阅读11分钟

概述

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

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


文件清单

文件行数功能
toolName.ts2工具名称常量
BashTool.tsx1,143核心实现:Schema 定义、执行逻辑、结果处理
prompt.ts369模型使用说明(API description)
bashPermissions.ts2,621权限系统:规则匹配、Classifier、模式处理
bashSecurity.ts2,592安全检查:命令注入、危险模式检测
bashCommandHelpers.ts265复合命令权限拆解
pathValidation.ts1,303路径安全验证
readOnlyValidation.ts1,990只读命令合法性校验
sedValidation.ts684sed 命令参数白名单校验
sedEditParser.ts322sed 替换命令解析器
modeValidation.ts115权限模式(acceptEdits 等)校验
shouldUseSandbox.ts153Sandbox 启用决策
commandSemantics.ts140命令退出码语义解释
destructiveCommandWarning.ts102危险命令 UI 警告
commentLabel.ts13Bash 注释标签提取
UI.tsx184工具使用和进度 UI 渲染
BashToolResultMessage.tsx190工具结果 UI 渲染
utils.ts223工具函数集合

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

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)

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

定义四个命令分类集合:

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)

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(避免模块加载时的循环依赖):

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)

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)

async prompt() { return getSimplePrompt(); }

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

isConcurrencySafe()(434-436)

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)

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)

if (input._simulatedSedEdit) {
  return applySedEdit(input._simulatedSedEdit, toolUseContext, parentMessage);
}

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

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

const commandGenerator = runShellCommand({ input, abortController, ... });

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

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

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)

const extracted = extractClaudeCodeHints(strippedStdout, input.command);

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

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

let isImage = isImageOutput(strippedStdout);
if (isImage) {
  const resized = await resizeShellImageOutput(strippedStdout, ...);
}

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

2.7 runShellCommand 生成器(826-1036)

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

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

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):

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:

// 结构化内容(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 内容结构

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()

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

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

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() 从命令中提取稳定的"命令+子命令"前缀用于规则建议:

'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)

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

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)

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
validateIncompleteCommands1Tab 开头、以操作符开头、以 flag 开头ask
validateJqSystemFunction2jq 的 system() / exec() 调用ask
validateJqFileArguments3jq -f 从文件加载ask
validateObfuscatedFlags4混淆标志(0x2D 等)ask
validateShellMetacharacters5shell 元字符反引号ask
validateDangerousVariables6IFS=, PATH=, LD_PRELOAD=deny
validateNewlines7命令内嵌换行符ask
validateDangerousPatterns8-10命令替换、输入/输出重定向ask/deny
validateIfsInjection11IFS 字段分隔符注入ask
validateGitCommitSubstitution12git commit 内容替换ask
validateProcEnvironAccess13/proc/self/environ 读取ask
validateMalformedTokens14shell-quote 畸形 tokendeny
validateBackslashWhitespace15反斜杠转义的空白字符ask
validateBraceExpansion16花括号展开(泄露路径)ask
validateControlCharacters17控制字符deny
validateUnicodeWhitespace18Unicode 空白字符ask
validateMidWordHash19词中注释符号ask
validateZshDangerousCommands20zsh 危险内建命令deny
validateBackslashEscapedOperators21反斜杠转义的操作符ask
validateCommentQuoteDesync22注释/引号失配ask
validateQuotedNewline23引号内换行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)的分段权限检查:

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 行)

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 行)

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

全路径验证(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:

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()

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 命令白名单

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 解析为结构化信息:

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()

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 命令退出码语义表

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

13.2 interpretCommandResult()

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 检测模式

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 行)

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

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

# Install dependencies
npm install

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


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

16.1 导出的渲染函数

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 函数清单

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,调用者负责降级

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

模型决定调用 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. 不存在客户端自动重试:失败返回给模型自主决定