概述
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.ts和BashTool.tsx相互引用) - 工具在 Anthropic API 中注册为
name: 'Bash',模型通过名称调用
2. BashTool.tsx------核心实现(1,143 行)
这是 BashTool 的主文件,包含:Schema 定义、buildTool() 配置、命令执行引擎。
2.1 导入阶段(1-52)
依赖的关键模块:
zod:定义输入输出 schemaShell.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...
设计意图:这些分类用于:
isSearchOrReadBashCommand():判断命令是否可折叠显示(UI 层面)isSilentBashCommand():判断命令是否预期无输出(显示"Done"替代"(No output)")
2.3 isSearchOrReadBashCommand(95-172)
typescript
export function isSearchOrReadBashCommand(command: string): {
isSearch: boolean;
isRead: boolean;
isList: boolean;
}
逐行逻辑:
splitCommandWithOperators(command)按&&,||,|,;分割复合命令- 跳过重定向目标(
> file后的 file) - 跳过语义中立命令(echo 等)
- 检查每个非中立段是否是搜索/读取/列表命令
- 所有非中立段都必须是搜索/读取命令才返回 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 系统准备命令模式匹配器:
- 使用
parseForSecurity(command)解析命令 AST - 提取所有子命令的 argv(去除前导 env var 赋值)
- 返回匹配函数,支持前缀匹配和通配符匹配
例如 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 5、sleep 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-541 的 checkPermissions() 进入):
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>
逐行逻辑:
- 多 cd 检测 (31-47):多个
cd命令需要用户确认 - cd + git 跨段攻击检测 (55-81):检测 cd 和 git 在不同管道段中(
cd sub && echo | git status),防止 bare repo fsmonitor 绕过 - 每段递归权限检查 (84-96):每段调
bashToolHasPermissionFn() - 拒绝聚合(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
判断命令是否只读的核心规则:
- 有 cd → 不是只读(会改变状态)
- 没有 cd → 检查所有子命令的 flag 是否在白名单内
- 有输出重定向 → 不是只读
- sed -i 编辑 → 不是只读
- 复合命令中任一子命令不是只读 → 整体不是只读
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
决策逻辑:
- 沙箱全局关闭 → false
dangerouslyDisableSandbox=true且策略允许绕过 → false- 无命令 → false
- 命令在被排除列表中(用户配置的
sandbox.excludedCommands)→ false - 否则 → true
12.2 containsExcludedCommand()
检查命令是否在排除列表中:
- 动态配置 (ant-only):
tengu_sandbox_disabled_commandsfeature flag - 用户配置 :
settings.json的sandbox.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 → 模型处理结果
关键安全设计模式
- 分层防御:权限规则 → 安全扫描 → 路径验证 → Classifier → 用户确认
- 默认拒绝 :任何安全检查失败都
deny或ask,绝不静默允许 - 内部字段隐藏 :
_simulatedSedEdit从模型 schema 中移除,防止绕过 - 命令拆分验证:复合命令的每段单独走完整权限检查链
- 跨段攻击检测:cd + git 在不同 pipe 段中的组合攻击检测
- 只读合并的保守策略:所有段都必须是只读命令才允许并发执行
- 不存在客户端自动重试:失败返回给模型自主决定