Claude Code Tool 系统设计拆解:从 buildTool 工厂到 Auto Mode 四层权限决策
Source: KoushikBaagh/claude-code-leak (GitHub, Mar 31, 2026) 警告: 本文仅供学习研究,请勿用于商业目的。
一、整体架构
Claude Code 的工具系统不是”一堆函数”,而是一套面向对象框架。核心类型定义在 src/Tool.ts,1332 个 TS 文件共用同一个抽象接口。
工具调用的完整生命周期:
用户输入
↓
QueryEngine 解析
↓
canUseTool(permissionContext) 权限检查
├─ 规则匹配层(permissionRuleParser)
├─ 只读白名单层(SAFE_YOLO_ALLOWLISTED_TOOLS)
└─ LLM 分类器层(yoloClassifier / Sonnet)
↓
Tool.call(args, context, canUseTool, parentMessage, onProgress)
↓
ToolResult<Output> → 渲染为 ToolUseBlock
↓
追加 messages → 继续下一个 Tool 或回复用户
二、核心类型:buildTool 工厂模式
所有工具出口必须走 buildTool() 工厂,自动填充安全默认值。这是个极聪明的设计——工具开发者只需要关注核心逻辑,安全默认值由框架兜底。
// src/Tool.ts
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: (_) => false, // 默认不安全
isReadOnly: (_) => false, // 默认会写
isDestructive: (_) => false,
checkPermissions: (_, __) => ({ behavior: 'allow', updatedInput: _ }),
toAutoClassifierInput: (_) => '',
userFacingName: () => def.name,
}
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return { ...TOOL_DEFAULTS, userFacingName: () => def.name, ...def } as BuiltTool<D>
}
为什么聪明?isReadOnly: false → 如果忘了实现,权限系统默认为”需要审批”;isDestructive 有值 → UI 直接显示警告图标,无需手动维护。
三、Tool 接口全解
type Tool<Input, Output, P> = {
// 身份
name: string
aliases?: string[] // 向后兼容的别名
searchHint?: string // ToolSearch 关键词(3-10词)
// 核心方法
call(args, context, canUseTool, parentMessage, onProgress): Promise<ToolResult<Output>>
description(input, options): Promise<string> // LLM 看到的工具描述
// Schema(Zod → JSON Schema → LLM prompt)
inputSchema: Input
inputJSONSchema?: ToolInputJSONSchema // MCP 兼容
// 安全四件套
isConcurrencySafe(input): boolean // 可并发执行?
isReadOnly(input): boolean // 只读?
isDestructive?(input): boolean // 破坏性(删除/覆盖)?
isOpenWorld?(input): boolean // 访问外部网络?
// 行为控制
isEnabled(): boolean
interruptBehavior?(): 'cancel' | 'block' // 工具运行时用户发消息
isSearchOrReadCommand?(input): { isSearch, isRead, isList } // UI 折叠
requiresUserInteraction?(): boolean
shouldDefer?: boolean // 延迟加载(需 ToolSearch 触发)
alwaysLoad?: boolean // 第一轮 prompt 就出现
}
四、58 个工具完整分类
Agent 编排(7个)
| 工具 | 功能 |
|---|---|
| Agent | 启动子 Worker Agent,最核心的编排工具 |
| SendMessage | 向已存在的 Worker 发消息(不启动新 Agent) |
| TeamCreate | 创建多 Agent 并行团队(Swarm 模式) |
| TeamDelete | 删除团队 |
| ListPeers | 列出同组 Agent |
| Skill | Agent Skills(/commit /verify 等内置技能) |
| Workflow | 工作流脚本自动化 |
文件读写(5个)
| 工具 | 功能 |
|---|---|
| Read | 读取文件,支持大文件截断 + 行号指定 |
| Edit | 文件修改,支持 sed/块编辑,有 destructive 检测 |
| Write | 覆盖式写入(原子操作:写临时文件 → rename) |
| Glob | 文件名模式匹配(ANT 内嵌 ripgrep) |
| Grep | 代码搜索(ANT 内嵌 ugrep,支持正则) |
任务管理(6个)
| 工具 | 功能 |
|---|---|
| TaskCreate | 创建任务 |
| TaskGet | 获取任务详情 |
| TaskUpdate | 更新任务状态 |
| TaskList | 列出任务 |
| TaskStop | 停止任务 |
| TaskOutput | 获取任务输出 |
其他分类
| 类别 | 工具 | 数量 |
|---|---|---|
| Plan Mode | EnterPlanMode / ExitPlanMode / ExitPlanModeV2 | 3 |
| Git | EnterWorktree / ExitWorktree | 2 |
| Web | WebFetch / WebSearch / WebBrowser | 3 |
| MCP 协议 | MCP / ListMcpResources / ReadMcpResource / McpAuth | 4 |
| Shell | Bash / PowerShell / REPL | 3 |
| 调度/触发 | ScheduleCron / CronDelete / CronList / RemoteTrigger | 4 |
| 通信 | SendUserFile / PushNotification / SubscribePR | 3 |
| 其他 | TodoWrite / AskUserQuestion / LSP / ToolSearch / Brief 等 | 8+ |
五、Auto Mode 权限决策流水线
这是 Claude Code 最有技术含量的设计——用 LLM 做安全分类。
工具调用请求
↓
第一层:权限规则(.claude.json)
├─ matchesAllowRule → 直接执行
└─ matchesSoftDeny → 拦截确认
↓
第二层:acceptEdits 快速通道
└─ 工具是 Edit + 路径在 CWD 内 → 自动批准
↓
第三层:只读工具白名单
└─ SAFE_YOLO_ALLOWLISTED_TOOLS.has(toolName) → 直接执行
包括:Read / Grep / Glob / LSP / Task* / PlanMode / ...
↓
第四层:LLM 分类器(yoloClassifier)
└─ classifyYoloAction() → Sonnet 判断 → allow / soft_deny / ask
分类器的 prompt 本身精心设计,包含三类规则:
type AutoModeRules = {
allow: string[], // 自动批准的行为
soft_deny: string[], // 拦截确认的行为
environment: string[], // 环境上下文,帮助分类器判断
}
六、MCP 协议接入
Claude Code 不是一个封闭系统——任何实现 MCP 协议的工具都可以接入:
// 工具定义通过 MCP JSON-RPC schema 自动转换
type MCPServerConnection = {
name: string
tools: Array<{
name: string
description: string
inputSchema: JSONSchema
}>
}
// MCP 工具和内置工具使用同一套 Tool 接口
// 协议无关,LLM 看到的是统一的工具描述
七、ToolSearch 延迟加载
shouldDefer?: boolean // 需 ToolSearch 关键词触发
alwaysLoad?: boolean // 即使标记 deferred 也要第一轮出现
初始 prompt → 只有 alwaysLoad 工具 + 白名单工具
用户需要某个工具 → 先调用 ToolSearch
ToolSearch 返回匹配的工具 → 触发目标工具加载
这样设计的好处:避免 58 个工具塞满 context window。
八、工程设计亮点
1. 条件导入 + DCE
// Bun 的 bundle 工具在构建时做静态分析
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
? require('./tools/SleepTool/SleepTool.js').SleepTool
: null
// 不符合 feature gate 的工具永远不进入产物
2. src/tools.ts 统一注册
export function getAllBaseTools(): Tools {
return [
AgentTool,
BashTool,
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []),
...(isTodoV2Enabled() ? [TaskCreateTool, ...] : []),
// ...
]
}
3. fail-closed 原则
isConcurrencySafe: (_) => false, // 默认不安全,宁可多审批
isReadOnly: (_) => false, // 默认需要写权限
九、对复刻的启发
| 设计 | 可复刻性 | 难度 |
|---|---|---|
| Zod schema → JSON schema → LLM prompt | ✅ | 低 |
buildTool 安全默认值工厂 | ✅ | 低 |
| 管道命令只读性递归判断 | ⚠️ | 高(需完整 Bash AST) |
| MCP 协议自动接入 | ✅ | 中 |
| ToolSearch 延迟加载 | ✅ | 中 |
| Agent 多进程编排 + session 管理 | ⚠️ | 高 |
| LLM 安全分类器(yoloClassifier) | ✅ | 中(需 Prompt 工程) |
| feature gate + DCE | ✅ | 低 |
结语
Claude Code 的工具系统设计验证了一个原则:越通用的能力,越需要精心设计的安全默认值。
把 58 个工具的统一接口、权限决策、安全默认值全部收敛到一个 buildTool 函数里——这才是工程化的正确姿势。不是”我有很多工具”,而是一个框架可以安全地扩展任意多工具。
本文源码分析基于泄露版本(Mar 31, 2026),请勿用于商业目的。
补充:BashTool 深度拆解
源码文件:
src/tools/BashTool/,共 9 个子模块,~3000 行核心逻辑
10.1 整体安全架构
BashTool 的安全体系分为五层,从命令解析到执行:
用户输入命令
↓
Layer 1: bashSecurity.ts — 基础安全分析(22 个检查器)
Layer 2: bashPermissions.ts — 用户规则匹配 + LLM 分类器
Layer 3: readOnlyValidation.ts — 只读命令白名单
Layer 4: pathValidation.ts — 路径约束检查
Layer 5: shouldUseSandbox.ts — 沙箱决策
↓
执行 / 弹窗确认 / 拒绝
10.2 Layer 1:bashSecurity.ts(基础安全分析)
这是最核心的模块,22 个安全检查器,逐个过滤恶意命令。
核心数据结构
// 验证上下文:同一个命令的多个"视角"
type ValidationContext = {
originalCommand: string // 原始命令
baseCommand: string // 基础命令名(如 "jq")
unquotedContent: string // 双引号内容已展开
fullyUnquotedContent: string // 所有引号内容已剥离
fullyUnquotedPreStrip: string // 剥离重定向前的 fullyUnquoted
unquotedKeepQuoteChars: string // 保留引号边界符(用于检测 # 攻击)
treeSitter?: TreeSitterAnalysis // Tree-sitter AST 增强分析
}
22 个检查器完整清单
| ID | 检查器 | 攻击场景 |
|---|---|---|
| 1 | validateEmpty | 空命令直接放行 |
| 2 | validateIncompleteCommands | 命令片段(以 - / && / | 开头)拦截 |
| 3 | validateSafeCommandSubstitution | $(cat <<'EOF'...) 放心用 |
| 4 | validateGitCommit | git commit 消息中的 $() / 反引号注入 |
| 5 | validateJqCommand | jq --from-file / jq -f 执行任意代码 |
| 6 | validateObfuscatedFlags | $'-exec', ''"-exec", """-f" 混淆 flag 攻击 |
| 7 | validateShellMetacharacters | find -name "; rm -rf /" 注入 |
| 8 | validateDangerousVariables | 变量在 ` |
| 9 | validateCommentQuoteDesync | # 后跟引号导致后续 quote tracker 失步 |
| 10 | validateQuotedNewline | 引号内换行符 + 下一行以 # 开头 → 行被 strip |
| 11 | validateCarriageReturn | \r 导致 shell-quote 和 bash 分词不一致 |
| 12 | validateNewlines | 换行符分割多命令 |
| 13 | validateIFSInjection | $IFS 环境变量注入 |
| 14 | validateProcEnvironAccess | /proc/*/environ 读取敏感环境变量 |
| 15 | validateDangerousPatterns | $() / ` / ${} 命令替换 |
| 16 | validateRedirections | < / > 重定向到敏感文件 |
| 17 | validateBackslashEscapedWhitespace | echo\ test → 命令名变成 “echo test” |
| 18 | validateBackslashEscapedOperators | \; 绕过 splitCommand 双重解析 |
| 19 | validateUnicodeWhitespace | \u00A0 等 Unicode 空白符绕过检查 |
| 20 | validateMidWordHash | 'x'# → 'x#' 去掉引号后变注释 |
| 21 | validateBraceExpansion | {--upload-pack="touch /tmp/pwn",x} → git 任意写文件 |
| 22 | validateZshDangerousCommands | zmodload / zpty / sysopen 等 Zsh 特殊命令 |
关键攻击详解
① 混淆 Flag 攻击(最复杂)
# 正常:jq -f evil.jq ← 被阻止
# 混淆1:jq ''"-f" evil.jq ← 两个空串 + 包含 -f 的引号
# 混淆2:jq $'-f' evil.jq ← ANSI-C 引码引码
# 混淆3:jq """-f" evil.jq ← 三个引号拼接
代码里针对每种都单独写了检测正则。
② CR 注入(shell-quote vs bash 分词差异)
# shell-quote 把 \r 当空格 → tokenize 成:
# ['TZ=UTC', 'curl', 'evil.com'] ← 看起来是 echo 命令
# bash 实际执行:TZ='UTC\r' curl evil.com ← 绕过环境变量检查
③ Brace Expansion 绕过
# git ls-remote {--upload-pack="touch /tmp/pwn",test}
# 引号剥离后 → 检测工具只看到 {--upload-pack="touch
# /tmp/pwn",test},无异样
# bash 实际展开 → git --upload-pack="touch /tmp/pwn" test
10.3 Layer 2:bashPermissions.ts(权限规则引擎)
// 规则类型:精确匹配 / 前缀匹配 / 通配符匹配
type ShellPermissionRule = {
type: 'exact' | 'prefix' | 'wildcard'
pattern: string // exact: 完整命令; prefix: 前缀; wildcard: glob 模式
flags?: string[] // 允许的 flag
}
// 权限结果
type PermissionResult = {
behavior: 'allow' | 'ask' | 'deny'
updatedInput?: { command: string }
decisionReason?: PermissionDecisionReason
message?: string
}
10.4 Layer 3:readOnlyValidation.ts(只读命令白名单)
// git 只读命令
GIT_READ_ONLY_COMMANDS = Set([
'git status', 'git log', 'git diff', 'git show', 'git branch',
'git tag', 'git remote', ...
])
// docker 只读命令
DOCKER_READ_ONLY_COMMANDS = Set([
'docker ps', 'docker images', 'docker logs', 'docker inspect', ...
])
// gh 只读命令
GH_READ_ONLY_COMMANDS = Set([
'gh run list', 'gh issue list', 'gh pr list', ...
])
// fd 的 -x/--exec, -X/--exec-batch 被禁用(执行任意命令)
FD_SAFE_FLAGS = {
'-h': 'none', '--help': 'none', '-H': 'none', '--hidden': 'none',
'-g': 'none', '--glob': 'none',
// ⚠️ -x/--exec, -X/--exec-batch 被故意排除
}
10.5 Layer 4:pathValidation.ts(路径约束)
// 核心路径提取器
PATH_EXTRACTORS = {
cp: (args) => [args[1], args[2]], // cp src dest
mv: (args) => [args[1], args[2]], // mv src dest
rm: (args) => extractFlags(args, ['-r', '-d', '-f']),
'git add': (args) => args.filter(a => !a.startsWith('-')),
...
}
// 约束检查:路径是否在允许目录内,是否是敏感路径,是否包含 ..
10.6 Layer 5:shouldUseSandbox.ts(沙箱决策)
function shouldUseSandbox(input: { command?: string }): boolean {
if (!SandboxManager.isSandboxingEnabled()) return false
if (input.dangerouslyDisableSandbox &&
SandboxManager.areUnsandboxedCommandsAllowed()) return false
if (containsExcludedCommand(input.command)) return false
return true
}
注意:排除列表是”用户体验功能”,不是安全边界。沙箱才是真正的安全控制。
10.7 commandSemantics.ts(退出码语义)
// 不同命令的退出码含义不同,防止误报
COMMAND_SEMANTICS = Map([
['grep', (exit) => exit >= 2 ? error : ok], // grep 返回 1 是"没找到",不是错误
['find', (exit) => exit >= 2 ? error : ok], // find 返回 1 是"部分无权限"
['diff', (exit) => exit >= 2 ? error : ok], // diff 返回 1 是"有差异"
['test', (exit) => exit >= 2 ? error : ok], // test 返回 1 是"条件为假"
])
10.8 工程设计亮点
| 设计 | 亮点 |
|---|---|
| Tree-sitter 双重分析 | 正则 + AST 同时跑,发现差异则记录日志 |
| 渐进式验证 | passthrough 继续往下走,allow 提前返回,ask 才弹窗 |
| Misparsing 标记 | shell-quote 和 bash 分词不一致的命令,标记 isBashSecurityCheckForMisparsing: true,直接拦截 |
| Heredoc 快速通道 | <<'DELIM' 的 heredoc 直接放行,跳过所有检查 |
| 防御深度 | 22 个检查器重叠覆盖,即使绕过了一个还有其他 |
| 失败安全 | 任何解析错误默认弹窗确认,不静默放行 |
10.9 对复刻的启发
BashTool 的设计思路完全可以用在其他 Agent 系统里:
1. 命令分词(splitCommand)→ 对应你的 exec 封装
2. 只读判定(readOnlyValidation)→ 对应你的 isReadOnly 实现
3. 安全检查(bashSecurity 22 个检查器)→ 对应你的黑名单规则
4. 路径约束(pathValidation)→ 对应你的目录越界检查
5. 沙箱(shouldUseSandbox)→ 对应你的 sandbox exec
最难复刻的是 bashSecurity.ts 里的 22 个检查器——每一个都对应一个真实的攻击向量,是多年实战积累出来的。如果是自研,直接用 libsandbox 或 bwrap 这类现成沙箱更实际。