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
SkillAgent 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 ModeEnterPlanMode / ExitPlanMode / ExitPlanModeV23
GitEnterWorktree / ExitWorktree2
WebWebFetch / WebSearch / WebBrowser3
MCP 协议MCP / ListMcpResources / ReadMcpResource / McpAuth4
ShellBash / PowerShell / REPL3
调度/触发ScheduleCron / CronDelete / CronList / RemoteTrigger4
通信SendUserFile / PushNotification / SubscribePR3
其他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检查器攻击场景
1validateEmpty空命令直接放行
2validateIncompleteCommands命令片段(以 - / && / | 开头)拦截
3validateSafeCommandSubstitution$(cat <<'EOF'...) 放心用
4validateGitCommitgit commit 消息中的 $() / 反引号注入
5validateJqCommandjq --from-file / jq -f 执行任意代码
6validateObfuscatedFlags$'-exec', ''"-exec", """-f" 混淆 flag 攻击
7validateShellMetacharactersfind -name "; rm -rf /" 注入
8validateDangerousVariables变量在 `
9validateCommentQuoteDesync# 后跟引号导致后续 quote tracker 失步
10validateQuotedNewline引号内换行符 + 下一行以 # 开头 → 行被 strip
11validateCarriageReturn\r 导致 shell-quote 和 bash 分词不一致
12validateNewlines换行符分割多命令
13validateIFSInjection$IFS 环境变量注入
14validateProcEnvironAccess/proc/*/environ 读取敏感环境变量
15validateDangerousPatterns$() / ` / ${} 命令替换
16validateRedirections< / > 重定向到敏感文件
17validateBackslashEscapedWhitespaceecho\ test → 命令名变成 “echo test”
18validateBackslashEscapedOperators\; 绕过 splitCommand 双重解析
19validateUnicodeWhitespace\u00A0 等 Unicode 空白符绕过检查
20validateMidWordHash'x'#'x#' 去掉引号后变注释
21validateBraceExpansion{--upload-pack="touch /tmp/pwn",x} → git 任意写文件
22validateZshDangerousCommandszmodload / 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 个检查器——每一个都对应一个真实的攻击向量,是多年实战积累出来的。如果是自研,直接用 libsandboxbwrap 这类现成沙箱更实际。