乐于分享
好东西不私藏

第四章验证清单:工具系统源码逐条验证报告

第四章验证清单:工具系统源码逐条验证报告

阶段三验证清单:工具系统源码逐条验证报告

验证范围:第三阶段精读笔记(4.1-4.10)覆盖的原书第 4 章内容及 tools/ 工具目录源码

验证方法:逐条对照原书描述与实际源码(claude-code-sourcemap/restored-src/src/),给出源码函数名和代码片段作为证据


验证项 1:BashTool 是否真的有 7 层防御(命令验证→路径检查→Shell 注入→超时→沙箱→权限→输出截断)

结论:✅ 存在七层防御,但源码实际有 10 步检查,比原书 7 层更细粒度

详细验证

原书 4.7 节描述的七层防御与源码 bashToolHasPermission() 函数的对应关系:

原书 Layer
原书描述
源码实际函数
源码文件
Layer 1
命令解析与 AST 分析
parseForSecurityFromAst()
 + checkSemantics()
utils/bash/ast.ts
Layer 2
精确匹配权限规则
bashToolCheckExactMatchPermission()bashPermissions.ts
Layer 3
前缀/通配符权限规则
bashPermissionRule()
 + matchWildcardPattern()
bashPermissions.ts
Layer 4
路径检查
checkPathConstraints()pathValidation.ts
Layer 5
Sed 命令约束
checkSedConstraints()sedValidation.ts
Layer 6
只读命令自动放行
validateReadOnlyCommand()readOnlyValidation.ts
Layer 7
分类器与用户审批
classifyBashCommand()bashPermissions.ts
 + bashClassifier.ts

1.1 用户描述的 7 项与源码对照

用户描述
源码对应
是否匹配
说明
命令验证
Layer 1: AST 解析 + 六道预检查 + 语义检查
✅ 匹配
Tree-sitter 解析 + 控制字符/Unicode/反斜杠/Zsh/花括号预检查 + eval/exec/source 语义检查
路径检查
Layer 4: checkPathConstraints()
✅ 匹配
35 种 PathCommand 路径提取 + -- 端标志 + 危险路径检测 + cd+write 组合检测 + 重定向目标验证
Shell 注入
bashSecurity.ts
 23 种安全检查
⚠️ 部分匹配
Shell 注入检测属于 Layer 1 的组成部分(COMMAND_SUBSTITUTION/PROCESS_SUBSTITUTION/IFS_INJECTION 等),不是独立的防御层,而是 Layer 1 AST 解析的子步骤
超时
AsyncGenerator 2s 阈值 + 进度轮询
❌ 不属于安全防御层
超时是执行管理机制(runShellCommand 的 AsyncGenerator 流式执行),不是权限决策链的一环。源码中不存在"超时防御层"
沙箱
shouldUseSandbox.ts
 + SandboxManager
⚠️ 部分匹配
沙箱决策是 Layer 7 的子步骤(shouldUseSandbox() 在权限检查中调用),不是独立防御层。沙箱自动放行是步骤 3(模式验证后、精确匹配前)的旁路
权限
Layer 2-3: 精确匹配 + 前缀/通配符
✅ 匹配
deny/ask/allow 三种规则 + 不对称剥离策略(deny 激进剥离 vs allow 保守剥离)
输出截断
maxResultSizeChars
❌ 不属于安全防御层
输出截断是结果管理机制(工具结果超过阈值时持久化到磁盘),不是权限决策链的一环

1.2 源码实际的 10 步检查链

比原书 7 层更细粒度的完整检查链:

步骤 1:  Tree-sitter AST 解析(六道预检查 + PARSE_ABORTED fail-closed + 语义检查)步骤 2:  模式验证(acceptEdits 自动放行 7 种文件命令 / bypassPermissions/dontAsk 跳过)步骤 3:  沙箱自动放行(shouldUseSandbox + checkSandboxAutoAllow)步骤 4:  精确匹配(deny  拒绝 / ask  询问 / allow  放行)步骤 5:  前缀/通配符匹配(deny: stripAllLeadingEnvVars 激进剥离 / allow: stripSafeWrappers 保守剥离)步骤 6:  路径约束(35 种 PathCommand + -- 处理 + 危险路径 + cd+write 组合 + 重定向目标)步骤 7:  Sed 约束(双模式白名单: 行打印 + 替换 / denylist: w/W/e/E/ASCII/花括号/换行/注释)步骤 8:  只读自动放行(GIT/GH/RIPGREP/PYRIGHT/DOCKER/EXTERNAL 6 组白名单 + flag 白名单 + UNC 检测)步骤 9:  分类器审批(LLM 分类器 + pendingClassifierCheck + 用户审批 UI)步骤 10: 破坏性命令警告(15 种模式,纯信息性,不影响权限决策)

1.3 关键差异总结

  • 用户描述中的"Shell 注入"是 Layer 1 的子步骤,不是独立层
  • "超时"和"输出截断"是执行管理机制,不属于七层安全防御链
  • "沙箱"是 Layer 7 的子步骤,在权限决策链内调用
  • 源码实际有 10 步(含模式验证和沙箱自动放行),比原书描述的 7 层更精细
  • 防御链的核心设计是 Fail-Closed:AST 解析失败 → too-complex → ask;Tree-sitter 超时 → PARSE_ABORTED → ask(不降级到 legacy)

1.4 源码证据

AST 解析失败 fail-closedutils/bash/ast.ts):

// Tree-sitter 解析超时或失败时返回 PARSE_ABORTED,不降级到 legacy parserif (parseResult === PARSE_ABORTED) {  return { behavior: 'ask', reason: 'parse-aborted' };  // Fail-Closed}

不对称剥离策略bashPermissions.ts):

// deny 规则:激进剥离(stripAllLeadingEnvVars — 剥离所有前导环境变量)const denyResult = matchDenyRules(input, stripAllLeadingEnvVars(command));// allow 规则:保守剥离(stripSafeWrappers — 仅剥离安全的包裹命令)const allowResult = matchAllowRules(input, stripSafeWrappers(command));

验证项 2:Fail-Closed 是否体现在"工具默认不可用,必须显式注册"

结论:❌ 不是。Fail-Closed 体现在安全属性的默认值选最严格选项,而非工具的可用性

详细验证

2.1 TOOL_DEFAULTS 的实际默认值

源码 Tool.ts 中的 TOOL_DEFAULTS 对象:

const TOOL_DEFAULTS = {  isEnabled() => true,                           // ✅ 默认启用(不是不可用)  isConcurrencySafe(_input?: unknown) => false,  // 🔒 保守:假设不安全  isReadOnly(_input?: unknown) => false,         // 🔒 保守:假设会写入  isDestructive(_input?: unknown) => false,      // 默认非破坏性  checkPermissions(input, _ctx?) =>    Promise.resolve({ behavior'allow'updatedInput: input }),  // 默认放行给通用权限系统  toAutoClassifierInput(_input?: unknown) => '',  // 默认跳过分类器  userFacingName(_input?: unknown) => '',          // 默认空}

2.2 各安全属性的默认值语义

属性
默认值
含义
Fail-Closed 体现
isConcurrencySafefalse
新工具默认串行执行
✅ 保守假设不安全,避免并发竞态
isReadOnlyfalse
新工具默认需权限检查
✅ 保守假设会写入,触发权限检查
isDestructivefalse
默认非破坏性
⚠️ 这不是 Fail-Closed(实际是"默认安全")
checkPermissionsallow
 (passthrough)
默认放行给通用权限系统
✅ 工具特定权限只是补充,通用权限系统是主要守门人
isEnabledtrue
默认启用
❌ 这恰恰是"默认可用"

2.3 工具注册的实际机制

tools.ts 的 getAllBaseTools() 函数:

export function getAllBaseTools(): Tools {  return [    AgentTool,           // ← 默认注册    BashTool,            // ← 默认注册    FileReadTool,        // ← 默认注册    FileEditTool,        // ← 默认注册    // ... 40+ 工具默认注册    ...(isTodoV2Enabled() ? [TaskCreateTool, ...] : []),  // 条件注册    ...(isAgentSwarmsEnabled() ? [getTeamCreateTool(), ...] : []),  ]}

工具默认在注册表中,但通过三种机制过滤:

  1. isEnabled() feature flag
    :如 isTodoV2Enabled()isAgentSwarmsEnabled()isBriefEnabled() 等
  2. Deny 规则过滤
    filterToolsByDenyRules() 在组装时移除被 deny 的工具
  3. 四维模式过滤
    :Simple 模式仅保留 Bash/Read/Edit;REPL 模式隐藏原始工具强制走 REPL;Coordinator 模式额外暴露 Agent/TaskStop

2.4 原书的精准表述

原书 4.3.1 节明确指出:

不对称的代价——一次误判"安全"可能导致数据丢失,而一次误判"危险"最多让用户多点一次确认按钮。

Fail-Closed 的本质是安全属性的默认值选最严格选项,而非工具的可用性控制:

场景
默认值
Fail-Closed 体现
新工具不知道是否并发安全
isConcurrencySafe: false
 → 串行执行
最安全选择
新工具不知道是否只读
isReadOnly: false
 → 触发权限检查
最安全选择
AST 解析失败
too-complex
 → ask(不放行)
最安全选择
Tree-sitter 超时
PARSE_ABORTED
 → ask(不降级到 legacy)
最安全选择
未知 flag
fail closed(不 fall-through)
最安全选择
未知 Skill 属性
默认需要权限(SAFE_SKILL_PROPERTIES 白名单外)
最安全选择

2.5 在 30+ 工具中的广泛体现

工具
Fail-Closed 场景
源码
ExitWorktreeTool
countWorktreeChanges()
 返回 null(无法确定状态)→ 拒绝删除
ExitWorktreeTool.ts
ExitPlanModeV2Tool
auto 模式 circuit breaker → 回退到 default(不放行)
ExitPlanModeV2Tool.ts
SkillTool
新属性不在 SAFE_SKILL_PROPERTIES 白名单 → 默认需权限
SkillTool.ts
SendMessageTool
bridge 目标 → bypass-immune(即使用户设 bypass 也需询问)
SendMessageTool.ts
CronCreateTool
MAX_JOBS = 50
 硬上限 → 超过拒绝
CronCreateTool.ts

验证项 3:工具基类是否统一了 inputSchema/execute/validate 三段式

结论:✅ 是,但实际是四段式(inputSchema → validateInput → checkPermissions → call)

详细验证

源码中 Tool<Input, Output, P> 泛型接口统一了所有工具的生命周期契约,包含 30+ 方法/属性。核心的执行管线是四段式:

3.1 四段式管线

阶段
接口方法
职责
源码位置
Schema 定义inputSchema: ZodType
辐射式定义输入参数的运行时验证 schema
Tool.ts
自定义验证validateInput(input, context): Promise<ValidationResult>
工具特定的业务逻辑验证(如 FileEditTool 的 read-before-write 检查)
Tool.ts
权限决策checkPermissions(input, context): Promise<PermissionResult>
工具特定的权限检查(默认放行给通用权限系统)
Tool.ts
执行call(args, context, canUseTool, parentMessage, onProgress): Promise<ToolResult>
核心执行逻辑,返回包含 data + contextModifier 的结果
Tool.ts

3.2 九步执行管线中的映射

toolExecution.ts 中的完整九步执行管线:

步骤 1: findToolByName(含别名回退)          ← 工具发现步骤 2: Abort 检查(用户中断)                ← 前置检查步骤 3: Zod Schema 验证 ← inputSchema          ← 第 1 段:Schema 定义步骤 4: tool.validateInput()                   ← 第 2 段:自定义验证步骤 5: Bash 分类器投机预检                    ← 优化(不阻塞)步骤 6: backfillObservableInput(路径回填)    ← 输入预处理步骤 7: PreToolUse Hooks                       ← Hook 拦截步骤 8: 权限决策解析 ← checkPermissions        ← 第 3 段:权限决策步骤 9: tool.call() + PostToolUse Hooks        ← 第 4 段:执行

3.3 三段式 vs 四段式的辨析

  • "三段式"描述
    (inputSchema / execute / validate)将 checkPermissions 归入 validate 的广义范畴,因为权限检查也是一种验证(验证是否有权执行)。这是概念层面的合理简化。
  • "四段式"实际
    :源码将 validateInput(业务逻辑验证)和 checkPermissions(权限验证)分为两个独立方法,因为它们的决策路径不同: 
    • validateInput
       返回 ValidationResult(通过/不通过)— 二值决策
    • checkPermissions
       返回 PermissionResult(allow/deny/ask)— 三值决策,决策空间更大

3.4 buildTool() 工厂函数的统一机制

export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {  return {    ...TOOL_DEFAULTS,          // 1. 先铺七个默认值    userFacingName() => def.name,  // 2. name 作为默认 userFacingName    ...def,                    // 3. 开发者定义覆盖  } as BuiltTool<D>}

所有 40+ 工具(BashTool、FileEditTool、AgentTool、TodoWriteTool、AskUserQuestionTool、SkillTool…)都通过 buildTool() 创建,统一了:

  1. 输入定义
    inputSchema 使用 Zod schema(z.strictObject() / z.object().passthrough()
  2. 输入验证
    validateInput 可选的自定义验证
  3. 权限检查
    checkPermissions 可选的工具特定权限逻辑
  4. 执行逻辑
    call() 核心执行方法
  5. 结果格式化
    mapToolResultToToolResultBlockParam 结果到 LLM 可读文本的转换

3.5 编译期类型安全

ToolDef → BuiltTool<D> 的类型体操:

// 开发者侧:7 个默认方法可选type ToolDef<InputOutputP=  Omit<Tool<InputOutputP>, DefaultableToolKeys&    // 非默认方法必须提供  Partial<Pick<Tool<InputOutputP>, DefaultableToolKeys>>  // 默认方法可选// 调用方侧:7 个默认方法必需(-? 移除可选性)type BuiltTool<D= Omit<DDefaultableToolKeys& {  [K in DefaultableToolKeys]-?:  // 所有默认键变为必需    K extends keyof D      ? undefined extends D[K? ToolDefaults[K] : D[K]      : ToolDefaults[K]}

这意味着调用方永远不需要 null 检查——tool.checkPermissions 一定存在(要么是开发者提供的实现,要么是 TOOL_DEFAULTS 的默认值),编译期类型系统保证了这一点。

3.6 统一契约在各工具中的实例

工具
inputSchema
validateInput
checkPermissions
call
BashTool
z.object({ command, timeout, ... })
六道预检查 + 语义检查
bashToolHasPermission()runShellCommand()
FileEditTool
z.strictObject({ filePath, oldString, newString, ... })
read-before-write 检查
allow
 (通用权限系统)
原子写入 + 时间戳校验
AgentTool
z.object({ description, prompt, subagent_type?, ... })
subagent_type 存在性
allowrunAgent()
ExitWorktreeTool
z.object({ action, discard_changes? })
worktree session null 检查
allow
keep/remove 分支
SkillTool
z.object({ skill, args? })
skill 存在性 + type 检查
deny/allow 规则 + SAFE_SKILL_PROPERTIES
inline/forked/remote 三模式
SendMessageTool
z.object({ to, summary?, message })
to 非空 + summary 必填 + 结构化消息不能广播
bridge → ask (bypass-immune) / 其他 → allow
6 种路由分支

3.7 补充:工具描述即 Prompt

除四段式执行管线外,工具还有两个重要的"可选但推荐"的属性:

// description 字段 — 本质是写给 LLM 的 Promptdescriptionstring | AsyncDynamicDescription// mapToolResultToToolResultBlockParam — 工具结果到 LLM 可读文本的转换mapToolResultToToolResultBlockParam(  toolResultToolResult,  contextToolContext) => Promise<ToolResultBlockParam[]>

这两个属性不参与安全决策,但直接影响 LLM 的工具选择行为和结果消费效率,属于原书 4.2.2 节"工具描述即 Prompt"设计模式的实现。


总结速查表

验证项
结论
关键发现
1. BashTool 七层防御
✅ 存在
源码实际有 10 步检查(比原书 7 层更细粒度);"Shell 注入"是 Layer 1 子步骤、"超时"和"输出截断"是执行管理机制(不属于安全防御链)、"沙箱"是 Layer 7 子步骤
2. Fail-Closed
❌ 不是"默认不可用"
工具默认在注册表中(isEnabled: true);Fail-Closed 体现在安全属性默认值isConcurrencySafe: falseisReadOnly: false——“不对称的代价”:误判安全可能导致数据丢失,误判危险最多多点一次按钮
3. 三段式统一
✅ 是
实际是四段式(inputSchema → validateInput → checkPermissions → call);通过 buildTool() + TOOL_DEFAULTS + ToolDef/BuiltTool 编译期类型体操实现"运行时防御转移到编译期约束",调用方永远不需要 null 检查

参考笔记

  • 4.1-4.2-tool-registration.md(工具注册表 + buildTool 泛型接口)
  • 4.3-bashtool-defense-chain.md(BashTool 七层防御链 + 10 步检查)
  • 4.4-file-edit-security.md(FileEditTool 双重时间戳校验)
  • 4.5-shared-security-logic.md(九步执行管线 + checkPermissions/validateInput 语义)
  • 4.6-readonly-tools.md(只读工具安全标记)
  • 4.7-mcp-tool.md(MCP 工具延迟加载)
  • 4.8-agent-tool.md(AgentTool 子代理执行)
  • 4.9-ssrf-protection.md(WebFetch SSRF 防护)
  • 4.10-tool-taxonomy.md(30+ 工具分类 + 安全属性矩阵 + 设计模式提炼)