BashTool 是 Claude Code 中最核心的工具之一,负责在受控环境中执行 Shell 命令。它实现了多层安全防护机制,包括 AST 级别的命令解析、细粒度权限控制、可插拔的沙箱系统,以及智能的只读命令识别。本页将深入剖析其架构设计、安全验证流程、沙箱机制以及执行流水线。
架构概览:多层防御体系
BashTool 采用**深度防御(Defense in Depth)**策略,通过多个独立的安全层逐级过滤和验证命令。每一层都可以阻止危险操作,确保只有安全的命令才能到达执行层。
graph TB
A[Tool Invocation] --> B[Input Validation]
B --> C[Permission Check]
C --> D{Security Validation}
D -->|Safe| E[Sandbox Decision]
D -->|Dangerous| F[Ask/Deny]
E -->|Sandbox Enabled| G[Sandbox Execution]
E -->|Sandbox Disabled| H[Direct Execution]
G --> I[Output Processing]
H --> I
I --> J[Result Interpretation]
J --> K[Return to Model]
subgraph Security Layers
D
D1[Tree-sitter AST Parsing]
D2[Dangerous Pattern Detection]
D3[Injection Prevention]
D4[Path Constraint Validation]
end
D --> D1
D1 --> D2
D2 --> D3
D3 --> D4
核心组件职责划分:
| 组件 | 文件路径 | 职责 |
|---|---|---|
| BashTool.tsx | src/tools/BashTool/BashTool.tsx | 工具入口、执行编排、结果映射 |
| bashSecurity.ts | src/tools/BashTool/bashSecurity.ts | AST 解析、危险模式检测、注入防御 |
| bashPermissions.ts | src/tools/BashTool/bashPermissions.ts | 权限规则匹配、分类器集成 |
| shouldUseSandbox.ts | src/tools/BashTool/shouldUseSandbox.ts | 沙箱启用决策逻辑 |
| sandbox-adapter.ts | src/utils/sandbox/sandbox-adapter.ts | 沙箱运行时适配器 |
| readOnlyValidation.ts | src/tools/BashTool/readOnlyValidation.ts | 只读命令白名单验证 |
| commandSemantics.ts | src/tools/BashTool/commandSemantics.ts | 退出码语义解释 |
Sources: BashTool.tsx, bashSecurity.ts, bashPermissions.ts, shouldUseSandbox.ts
安全验证流水线:从 AST 到执行
Tree-sitter AST 解析层
BashTool 使用 Tree-sitter 对 Shell 命令进行完整的语法树解析,而非简单的正则匹配。这种方法能准确识别命令结构,避免误报和绕过攻击。
sequenceDiagram
participant Tool as BashTool
participant Parser as Tree-sitter Parser
participant Validators as Security Validators
participant Permission as Permission System
Tool->>Parser: parseForSecurity(command)
Parser->>Parser: Build AST Tree
Parser->>Parser: Extract SimpleCommands
Parser-->>Tool: ParseResult
loop For each subcommand
Tool->>Validators: validateIncompleteCommands()
Validators-->>Tool: passthrough/ask
Tool->>Validators: validateDangerousPatterns()
Validators-->>Tool: passthrough/ask
Tool->>Validators: validateInjectionVectors()
Validators-->>Tool: passthrough/ask
end
Tool->>Permission: bashToolHasPermission()
Permission-->>Tool: allow/ask/deny
解析结果类型:
type ParseForSecurityResult =
| { kind: 'simple'; commands: SimpleCommand[]; redirects: Redirect[] }
| { kind: 'unavailable' } // Tree-sitter 不可用
| { kind: 'too-complex' } // 嵌套层级过深
AST 解析能够精确识别:
- 命令替换:
$(cmd)、`cmd`、<(cmd)、>(cmd) - 变量展开:
${var}、$var、$[arithmetic] - 重定向操作:
>file、>>file、<file、2>&1 - 复合命令:
cmd1 && cmd2、cmd1 || cmd2、cmd1 ; cmd2、cmd1 | cmd2
Sources: bashSecurity.ts, ast.ts
危险模式检测器
bashSecurity.ts 实现了 20+ 个独立的验证器,每个专注于特定攻击向量:
命令替换攻击向量:
$()命令替换:可能执行任意代码<()/>()进程替换:创建临时文件描述符${}参数展开:可能触发间接执行- Zsh 特有:
=cmd展开为$(which cmd),可绕过二进制黑名单
Zsh 危险命令黑名单:
const ZSH_DANGEROUS_COMMANDS = new Set([
'zmodload', // 模块加载器 - 访问危险模块的入口
'emulate', // 模拟模式 - 可执行任意代码
'sysopen', // 系统调用 - 文件描述符操作
'sysread', // 读取任意 fd
'syswrite', // 写入任意 fd
'zpty', // 伪终端 - 命令执行
'ztcp', // TCP 连接 - 网络外泄
'zsocket', // Unix socket - IPC 攻击
// ... 更多 zsh/files 内置命令
])
引号注入检测: 系统会检测引号状态不同步攻击,例如:
echo 'unclosed
# 后续命令会被误解析
Sources: bashSecurity.ts, bashSecurity.ts
Heredoc 安全验证
Heredoc(Here Document)是 Shell 中的多行文本输入机制,但也可被滥用执行任意代码。BashTool 实现了严格的 heredoc 安全校验:
安全 Heredoc 模式:
# ✅ 安全:单引号定界符,无变量展开
result=$(cat <<'EOF'
This is literal text
No variable expansion: $HOME
EOF
)
危险 Heredoc 模式:
# ❌ 危险:未引用定界符,会展开变量和命令
cat <<EOF
Current dir: $(pwd)
User: $USER
EOF
验证器采用逐行匹配(而非正则),精确复现 Bash 的 heredoc 关闭行为:
- 定位
<<'DELIM'模式 - 逐行查找第一个完全匹配的定界符
- 验证定界符后只能有空白和
) - 确保
)出现在正确位置
Sources: bashSecurity.ts
沙箱系统:隔离执行环境
沙箱架构设计
BashTool 集成了 @anthropic-ai/sandbox-runtime 包,提供操作系统级别的隔离。沙箱使用 Bubblewrap(Linux)或 Seatbelt(macOS)限制进程权限。
graph LR
A[Command String] --> B{shouldUseSandbox?}
B -->|Yes| C[SandboxManager.wrapWithSandbox]
B -->|No| D[Direct Execution]
C --> E[Build Sandbox Config]
E --> F[Filesystem Restrictions]
E --> G[Network Restrictions]
E --> H[Resource Limits]
F --> I[Allow: Project Dir]
F --> J[Allow: Temp Dir]
F --> K[Deny: ~/.ssh, ~/.gnupg]
G --> L[Allow: Allowed Domains]
G --> M[Deny: All Other Networks]
H --> N[CPU/Memory Limits]
H --> O[Process Tree Isolation]
I --> P[Sandboxed Process Spawn]
P --> Q[Output Capture]
Q --> R[Violation Annotation]
沙箱启用决策树:
function shouldUseSandbox(input: SandboxInput): boolean {
// 1. 检查全局沙箱开关
if (!SandboxManager.isSandboxingEnabled()) return false
// 2. 检查显式禁用标志(需要策略允许)
if (input.dangerouslyDisableSandbox &&
SandboxManager.areUnsandboxedCommandsAllowed()) {
return false
}
// 3. 检查命令是否存在
if (!input.command) return false
// 4. 检查用户配置的排除命令
if (containsExcludedCommand(input.command)) return false
return true
}
Sources: shouldUseSandbox.ts, sandbox-adapter.ts
文件系统隔离
沙箱通过路径白名单和黑名单控制文件访问:
路径解析规则:
//path→ 绝对路径(从文件系统根开始)/path→ 相对于设置文件目录~/path→ 用户主目录./path或path→ 当前工作目录
文件系统配置转换:
// 从 Claude Code 设置转换为 SandboxRuntime 配置
function convertToSandboxRuntimeConfig(settings: SettingsJson) {
// 1. 提取网络域名白名单
const allowedDomains = extractAllowedDomains(settings)
// 2. 构建文件系统规则
const fsConfig = {
allowRead: resolveReadPaths(settings.permissions.allow),
denyRead: resolveReadPaths(settings.permissions.deny),
allowWrite: resolveWritePaths(settings.sandbox.filesystem.allowWrite),
denyWrite: resolveWritePaths(settings.sandbox.filesystem.denyWrite),
}
// 3. 应用策略设置覆盖
if (shouldAllowManagedReadPathsOnly()) {
fsConfig.allowRead = getPolicyReadPaths()
}
return { network: { allowedDomains }, filesystem: fsConfig }
}
临时目录隔离: 每个用户拥有独立的临时目录,防止多用户权限冲突:
const sandboxTmpDir = posixJoin(
process.env.CLAUDE_CODE_TMPDIR || '/tmp',
getClaudeTempDirName(), // 用户特定的目录名
)
await fs.mkdir(sandboxTmpDir, { mode: 0o700 }) // 仅所有者可访问
Sources: sandbox-adapter.ts, sandbox-adapter.ts, Shell.ts
网络隔离
沙箱限制进程的网络访问,只允许白名单域名:
网络域名提取:
// 从 WebFetch 权限规则提取域名
for (const ruleString of settings.permissions.allow) {
const rule = permissionRuleValueFromString(ruleString)
if (rule.toolName === 'WebFetch' &&
rule.ruleContent?.startsWith('domain:')) {
allowedDomains.push(rule.ruleContent.substring('domain:'.length))
}
}
策略模式:
当启用 allowManagedDomainsOnly 时,只使用策略设置中的域名,忽略用户配置:
if (shouldAllowManagedSandboxDomainsOnly()) {
// 仅使用 policySettings.sandbox.network.allowedDomains
const policySettings = getSettingsForSource('policySettings')
allowedDomains = policySettings?.sandbox?.network?.allowedDomains || []
}
Sources: sandbox-adapter.ts
沙箱违规处理
当沙箱进程违反限制时,系统会捕获违规事件并标注输出:
// 在输出中标注沙箱违规
const outputWithSbFailures = SandboxManager.annotateStderrWithSandboxFailures(
input.command,
result.stdout || ''
)
违规事件类型:
- 文件系统访问违规(读写被拒绝的路径)
- 网络连接违规(访问非白名单域名)
- 资源限制违规(CPU/内存超限)
Bare Git Repo 清理:
Linux 上的 Bubblewrap 会在工作目录创建 0 字节的挂载点文件(如 .bashrc、HEAD),这些”幽灵文件”在沙箱退出后仍然存在。系统会在命令执行后同步清理:
void shellCommand.result.then(async result => {
// 同步清理幽灵文件,确保调用者在同一微任务中看到干净的工作树
if (shouldUseSandbox) {
SandboxManager.cleanupAfterCommand()
}
// ... 后续处理
})
Sources: BashTool.tsx, sandbox-adapter.ts, Shell.ts
权限系统:细粒度访问控制
权限决策流程
BashTool 的权限系统基于规则引擎和分类器双重机制:
flowchart TD
A[Command Input] --> B{Check Rules}
B -->|Exact Match| C{Allow Rule?}
B -->|Prefix Match| D{Allow Prefix?}
B -->|No Match| E{Classifier Enabled?}
C -->|Yes| F[Allow]
C -->|No| G[Deny]
D -->|Yes| F
D -->|No| G
E -->|Yes| H[ML Classifier]
E -->|No| I[Ask User]
H -->|High Confidence| J{Allow/Deny}
H -->|Low Confidence| I
J -->|Allow| F
J -->|Deny| G
F --> K[Execute Command]
G --> L[Block Command]
I --> M[Show Permission Dialog]
规则匹配类型:
- 精确匹配:
Bash(git status)→ 仅匹配git status - 前缀匹配:
Bash(npm run:*)→ 匹配所有npm run子命令 - 通配符匹配:
Bash(docker *)→ 匹配所有docker开头的命令
Sources: bashPermissions.ts
命令前缀提取
对于多子命令工具(如 git commit、npm run),系统会智能提取稳定的前缀:
function getSimpleCommandPrefix(command: string): string | null {
const tokens = command.trim().split(/\s+/)
// 跳过安全的环境变量赋值
let i = 0
while (i < tokens.length && ENV_VAR_ASSIGN_RE.test(tokens[i])) {
const varName = tokens[i].split('=')[0]
if (!SAFE_ENV_VARS.has(varName)) {
return null // 非安全变量,回退到精确匹配
}
i++
}
const remaining = tokens.slice(i)
if (remaining.length < 2) return null
// 第二个 token 必须看起来像子命令(小写字母开头)
const subcmd = remaining[1]
if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(subcmd)) {
return null
}
return remaining.slice(0, 2).join(' ') // "git commit"
}
示例转换:
git commit -m "fix"→git commitNODE_ENV=prod npm run build→npm run(NODE_ENV在安全列表中)MY_VAR=val npm run build→null(MY_VAR不安全,回退到精确匹配)
Sources: bashPermissions.ts
复合命令拆分
对于 &&、||、; 连接的复合命令,系统会拆分为子命令并逐个验证:
// 拆分复合命令
const subcommands = splitCommand_DEPRECATED(command)
// 为每个子命令检查权限
for (const subcommand of subcommands) {
const result = checkSingleCommand(subcommand)
if (result.behavior === 'deny') {
return result // 任一子命令被拒绝,整个命令被拒绝
}
}
安全限制: 为防止拒绝服务攻击,系统限制最多检查 50 个子命令:
export const MAX_SUBCOMMANDS_FOR_SECURITY_CHECK = 50
超过限制的命令会回退到 ask 行为(安全默认)。
Sources: bashPermissions.ts
机器学习分类器
BashTool 集成了可选的 ML 分类器,用于自动判断命令安全性:
分类器输出:
type ClassifierResult = {
behavior: 'allow' | 'deny' | 'ask'
confidence: number // 0.0 - 1.0
matchedDescription?: string
reason?: string
}
分类器决策逻辑:
- 高置信度(> 0.9):自动允许或拒绝
- 低置信度(≤ 0.9):询问用户
分类器仅作为辅助决策,最终的权限规则优先级更高。如果规则明确允许,即使分类器认为危险也会执行。
Sources: bashPermissions.ts
只读命令识别:优化并发执行
只读命令白名单
BashTool 维护了详细的只读命令白名单,用于:
- 判断命令是否可安全并发执行
- 折叠显示搜索/读取类命令的输出
- 自动批准低风险操作
命令分类:
| 类别 | 命令示例 | 用途 |
|---|---|---|
| 搜索命令 | grep, rg, find, ag | 文本搜索、文件查找 |
| 读取命令 | cat, head, tail, less | 查看文件内容 |
| 列表命令 | ls, tree, du | 目录浏览 |
| 分析命令 | wc, stat, file, jq | 文件分析、数据处理 |
| 语义中性 | echo, printf, true, false | 纯输出/状态命令 |
管道验证:
对于管道命令 cmd1 | cmd2 | cmd3,所有部分都必须是只读命令,整体才被视为只读:
export function isSearchOrReadBashCommand(command: string) {
const partsWithOperators = splitCommandWithOperators(command)
for (const part of partsWithOperators) {
// 跳过操作符和重定向
if (['||', '&&', '|', ';', '>', '>>'].includes(part)) continue
const baseCommand = part.trim().split(/\s+/)[0]
// 语义中性命令不影响整体性质
if (BASH_SEMANTIC_NEUTRAL_COMMANDS.has(baseCommand)) continue
// 任一非只读命令导致整体为非只读
if (!isReadOnlyCommand(baseCommand)) {
return { isSearch: false, isRead: false, isList: false }
}
}
return { isSearch: true, isRead: true, isList: true }
}
Sources: BashTool.tsx, readOnlyValidation.ts
Flag 参数验证
许多命令的读写性质取决于 flag 参数。例如 git push 是写操作,但 git push --dry-run 是只读操作。
安全 Flag 白名单:
const FD_SAFE_FLAGS: Record<string, FlagArgType> = {
'-h': 'none', // 帮助
'--hidden': 'none', // 显示隐藏文件
'-t': 'string', // 文件类型过滤
'--exclude': 'string', // 排除模式
// 注意:-x/--exec 被排除,因为会执行任意命令
}
Flag 验证流程:
function validateFlags(
command: string,
args: string[],
config: CommandConfig
): boolean {
for (let i = 0; i < args.length; i++) {
const arg = args[i]
// 遇到 -- 停止解析(大多数工具)
if (arg === '--' && config.respectsDoubleDash !== false) {
break
}
// 检查是否在白名单中
const flagType = config.safeFlags[arg]
if (!flagType) {
return false // 未知 flag,拒绝
}
// 消费参数(如果有)
if (flagType !== 'none') {
i++ // 跳过参数值
}
}
return true
}
安全陷阱示例:
# ❌ 危险:-x 会执行任意命令
find . -name "*.sh" -x rm {}
# ✅ 安全:仅列出文件
find . -name "*.sh"
Sources: readOnlyValidation.ts
Git 命令特殊处理
Git 命令有特殊的读写语义,系统维护了详细的只读子命令列表:
只读 Git 命令:
git status,git log,git diff,git showgit branch --list,git tag --listgit remote --verbose,git config --list
写操作 Git 命令:
git commit,git push,git resetgit checkout,git merge,git rebase
边界情况:
# ✅ 只读:--dry-run 不修改任何内容
git push --dry-run
# ❌ 写操作:会修改工作树
git checkout -- .
Sources: readOnlyValidation.ts
命令执行流水线
执行器架构
BashTool 的执行层由多个协作组件构成:
sequenceDiagram
participant BT as BashTool
participant SE as Shell Executor
participant SB as Sandbox Manager
participant SP as Shell Provider
participant TO as TaskOutput
BT->>SE: exec(command, options)
SE->>SP: findSuitableShell()
SP-->>SE: shellPath, provider
alt Sandbox Enabled
SE->>SB: shouldUseSandbox(input)
SB-->>SE: true
SE->>SB: wrapWithSandbox(cmd, shell)
SB->>SB: Build Config
SB-->>SE: sandboxedCommand
end
SE->>TO: Create TaskOutput
SE->>SP: buildExecCommand(cmd, sandboxConfig)
SP-->>SE: commandString, cwdFilePath
SE->>SP: spawn(binary, args, options)
SP-->>SE: ChildProcess
loop Progress Updates
SP-->>TO: stdout/stderr chunks
TO-->>BT: onProgress callback
BT-->>BT: Yield progress event
end
SP-->>SE: Exit Code
SE->>TO: Read final output
TO-->>SE: stdout, stderr
SE-->>BT: ExecResult
Shell Provider 接口:
interface ShellProvider {
shellPath: string // Shell 二进制路径
detached: boolean // 是否分离进程
buildExecCommand(command, options): {
commandString: string
cwdFilePath: string
}
getSpawnArgs(commandString): string[]
getEnvironmentOverrides(command): Record<string, string>
}
内置 Provider:
- BashProvider:标准 Bash/Zsh 执行
- PowerShellProvider:Windows PowerShell 执行(Base64 编码命令)
工作目录恢复
命令执行可能导致工作目录变化(cd 命令),系统通过临时文件跟踪 CWD:
CWD 跟踪机制:
// Shell Provider 构建命令时会注入 pwd 追踪
const builtCommand = `
${userCommand}
pwd -P >| "${cwdFilePath}"
`
// 命令完成后读取新的 CWD
void shellCommand.result.then(async result => {
if (!preventCwdChanges && !result.backgroundTaskId) {
const newCwd = readFileSync(nativeCwdFilePath, 'utf8').trim()
// 验证新目录存在
await realpath(newCwd)
setCwdState(newCwd)
// 触发 Hook 系统通知
onCwdChangedForHooks(newCwd)
}
})
目录恢复逻辑: 如果当前 CWD 被删除(例如临时目录清理),系统会回退到原始启动目录:
try {
await realpath(cwd)
} catch {
const fallback = getOriginalCwd()
logForDebugging(`Shell CWD "${cwd}" no longer exists, recovering to "${fallback}"`)
setCwdState(fallback)
}
Sources: Shell.ts
进度流式传输
长时间运行的命令会实时流式传输输出,提供进度反馈:
进度信号机制:
// 创建进度唤醒信号
let resolveProgress: (() => void) | null = null
function createProgressSignal(): Promise<null> {
return new Promise(resolve => {
resolveProgress = () => resolve(null)
})
}
// Shell 执行器的进度回调
const shellCommand = await exec(command, signal, 'bash', {
timeout: timeoutMs,
onProgress(lastLines, allLines, totalLines, totalBytes, isIncomplete) {
lastProgressOutput = lastLines
fullOutput = allLines
// 唤醒 generator 以 yield 进度更新
if (resolveProgress) {
resolveProgress()
resolveProgress = null
}
},
shouldUseSandbox: shouldUseSandbox(input),
shouldAutoBackground: autoBackgroundingEnabled
})
// Generator 循环
while (true) {
await createProgressSignal()
yield {
type: 'progress',
output: lastProgressOutput,
fullOutput: fullOutput,
elapsedTimeSeconds: elapsedTime,
totalLines: lastTotalLines,
totalBytes: lastTotalBytes
}
}
自动后台化: 对于 Assistant 模式,长时间运行的阻塞命令会自动移至后台:
const ASSISTANT_BLOCKING_BUDGET_MS = 15_000 // 15 秒后自动后台化
if (shouldAutoBackground && elapsedTime > ASSISTANT_BLOCKING_BUDGET_MS) {
const backgroundTaskId = await moveTaskToBackground(shellCommand)
return {
...result,
backgroundTaskId,
assistantAutoBackgrounded: true
}
}
Sources: BashTool.tsx
输出处理与持久化
大型输出(> 30KB)会被持久化到磁盘,避免内存溢出:
输出持久化流程:
const MAX_PERSISTED_SIZE = 64 * 1024 * 1024 // 64 MB
if (result.outputFilePath && result.outputTaskId) {
const fileStat = await fsStat(result.outputFilePath)
persistedOutputSize = fileStat.size
// 截断超大文件
if (fileStat.size > MAX_PERSISTED_SIZE) {
await fsTruncate(result.outputFilePath, MAX_PERSISTED_SIZE)
}
// 复制到工具结果目录
const dest = getToolResultPath(result.outputTaskId, false)
await link(result.outputFilePath, dest) // 或 copyFile
persistedOutputPath = dest
}
// 为模型构建持久化输出消息
if (persistedOutputPath) {
const preview = generatePreview(stdout, PREVIEW_SIZE_BYTES)
processedStdout = buildLargeToolResultMessage({
filepath: persistedOutputPath,
originalSize: persistedOutputSize,
preview: preview.preview,
hasMore: preview.hasMore
})
}
图像输出处理: 命令输出可能包含图像(例如终端截图),系统会自动检测并压缩:
let isImage = isImageOutput(stdout)
if (isImage) {
const resized = await resizeShellImageOutput(stdout, result.outputFilePath)
if (resized) {
compressedStdout = resized
} else {
isImage = false // 压缩失败,作为文本处理
}
}
Sources: BashTool.tsx
命令语义解释
退出码语义映射
不同命令对退出码有不同的语义解释。BashTool 维护了命令特定的语义映射表:
语义映射配置:
const COMMAND_SEMANTICS: Map<string, CommandSemantic> = new Map([
// grep: 0=匹配, 1=无匹配, 2+=错误
['grep', (exitCode) => ({
isError: exitCode >= 2,
message: exitCode === 1 ? 'No matches found' : undefined
})],
// diff: 0=无差异, 1=有差异, 2+=错误
['diff', (exitCode) => ({
isError: exitCode >= 2,
message: exitCode === 1 ? 'Files differ' : undefined
})],
// find: 0=成功, 1=部分成功, 2+=错误
['find', (exitCode) => ({
isError: exitCode >= 2,
message: exitCode === 1 ? 'Some directories were inaccessible' : undefined
})],
// test/[: 0=真, 1=假, 2+=错误
['test', (exitCode) => ({
isError: exitCode >= 2,
message: exitCode === 1 ? 'Condition is false' : undefined
})]
])
语义解释流程:
function interpretCommandResult(
command: string,
exitCode: number,
stdout: string,
stderr: string
): { isError: boolean; message?: string } {
const semantic = getCommandSemantic(command)
return semantic(exitCode, stdout, stderr)
}
错误抛出逻辑:
如果命令被解释为错误,会抛出 ShellError:
if (interpretationResult.isError && !isInterrupt) {
throw new ShellError('', outputWithSbFailures, result.code, result.interrupted)
}
Sources: commandSemantics.ts, BashTool.tsx
破坏性命令警告
系统会检测潜在破坏性命令并显示警告(但不阻止执行):
破坏性模式检测:
const DESTRUCTIVE_PATTERNS: DestructivePattern[] = [
// Git 数据丢失
{ pattern: /\bgit\s+reset\s+--hard\b/,
warning: 'Note: may discard uncommitted changes' },
{ pattern: /\bgit\s+push\b[^;&|\n]*[ \t](--force|--force-with-lease|-f)\b/,
warning: 'Note: may overwrite remote history' },
{ pattern: /\bgit\s+clean\b(?![^;&|\n]*(?:-[a-zA-Z]*n|--dry-run))[^;&|\n]*-[a-zA-Z]*f/,
warning: 'Note: may permanently delete untracked files' },
// 文件删除
{ pattern: /(^|[;&|\n]\s*)rm\s+-[a-zA-Z]*[rR][a-zA-Z]*f/,
warning: 'Note: may recursively force-remove files' },
// 数据库操作
{ pattern: /\b(DROP|TRUNCATE)\s+(TABLE|DATABASE|SCHEMA)\b/i,
warning: 'Note: may drop or truncate database objects' },
// 基础设施
{ pattern: /\bkubectl\s+delete\b/,
warning: 'Note: may delete Kubernetes resources' },
{ pattern: /\bterraform\s+destroy\b/,
warning: 'Note: may destroy Terraform infrastructure' }
]
警告信息会显示在权限对话框中,提醒用户注意风险。
Sources: destructiveCommandWarning.ts
Sed 内联编辑特殊处理
BashTool 能够识别 sed -i 内联编辑命令,并将其转换为等效的文件编辑操作:
Sed 命令解析:
function parseSedEditCommand(command: string): SedEditInfo | null {
// 匹配模式:sed -i 's/old/new/' file.txt
const match = command.match(/^sed\s+-i\s+'([^']+)'?\s+(.+)$/)
if (!match) return null
const [, sedExpression, filePath] = match
// 解析 sed 表达式:s/old/new/
const sedMatch = sedExpression.match(/^s(.)(.+?)\1(.+?)\1(g?)$/)
if (!sedMatch) return null
const [, , oldString, newString, globalFlag] = sedMatch
return {
filePath,
oldString,
newString,
isGlobal: globalFlag === 'g'
}
}
转换为文件编辑:
// 在 validateInput 中检测 sed 命令
const sedInfo = parseSedEditCommand(input.command)
if (sedInfo) {
// 读取文件内容
const originalContent = await readFile(sedInfo.filePath, 'utf8')
// 执行替换
const newContent = sedInfo.isGlobal
? originalContent.replaceAll(sedInfo.oldString, sedInfo.newString)
: originalContent.replace(sedInfo.oldString, sedInfo.newString)
// 写入文件
await writeTextContent(sedInfo.filePath, newContent)
// 通知 VS Code
notifyVscodeFileUpdated(sedInfo.filePath, originalContent, newContent)
return { data: { stdout: '', stderr: '', interrupted: false } }
}
这种转换使得 sed -i 命令在 UI 中显示为文件编辑操作,而非普通 Bash 命令。
Sources: BashTool.tsx, sedEditParser.ts, sedValidation.ts
Claude Code Hints 协议
BashTool 实现了零令牌的插件推荐协议:CLI/SDK 通过 CLAUDECODE=1 环境变量标识,并在 stderr 中输出 <claude-code-hint /> 标签。
Hint 提取流程:
// 扫描输出中的 hint 标签
const extracted = extractClaudeCodeHints(strippedStdout, input.command)
strippedStdout = extracted.stripped // 从输出中移除标签
// 仅在主线程记录 hint(避免子代理污染)
if (isMainThread && extracted.hints.length > 0) {
for (const hint of extracted.hints) {
maybeRecordPluginHint(hint) // 记录以供推荐系统使用
}
}
Hint 标签格式:
<claude-code-hint
plugin-name="my-lsp"
install-command="npm install -g my-lsp"
reason="Provides better code intelligence for TypeScript"
/>
这个协议允许外部工具在不消耗模型令牌的情况下推荐插件安装。
Sources: BashTool.tsx
总结与最佳实践
BashTool 是 Claude Code 安全架构的核心组件,通过多层防御机制确保命令执行的安全性:
关键设计原则:
- 深度防御:AST 解析 → 安全验证 → 权限检查 → 沙箱隔离
- 最小权限:只读命令自动识别,沙箱默认拒绝所有访问
- 显式授权:未知命令询问用户,危险操作显示警告
- 零信任:所有输入都经过严格验证,不信任任何外部数据
配置建议:
- 启用沙箱:
sandbox.enabled: true - 配置排除命令:
sandbox.excludedCommands: ["bazel", "npm run"] - 设置网络白名单:
sandbox.network.allowedDomains: ["api.github.com"] - 使用策略设置:
policySettings覆盖用户配置
安全注意事项:
- 沙箱不是万能的:有经验的攻击者可能绕过限制
- 定期审查权限规则:移除不再需要的允许规则
- 监控沙箱违规:关注
SandboxViolation事件 - 使用
failIfUnavailable:确保沙箱可用时才执行命令
BashTool 的设计体现了”安全默认 + 灵活配置”的理念,在保护用户的同时保持易用性。通过理解其内部机制,开发者可以更好地利用这些安全特性,构建可靠的自动化工作流。