第四章验证清单:工具系统源码逐条验证报告
阶段三验证清单工具系统源码逐条验证报告验证范围第三阶段精读笔记4.1-4.10覆盖的原书第 4 章内容及tools/工具目录源码验证方法逐条对照原书描述与实际源码claude-code-sourcemap/restored-src/src/给出源码函数名和代码片段作为证据验证项 1BashTool 是否真的有 7 层防御命令验证→路径检查→Shell 注入→超时→沙箱→权限→输出截断结论✅ 存在七层防御但源码实际有 10 步检查比原书 7 层更细粒度详细验证原书 4.7 节描述的七层防御与源码bashToolHasPermission()函数的对应关系原书 Layer原书描述源码实际函数源码文件Layer 1命令解析与 AST 分析parseForSecurityFromAst()checkSemantics()utils/bash/ast.tsLayer 2精确匹配权限规则bashToolCheckExactMatchPermission()bashPermissions.tsLayer 3前缀/通配符权限规则bashPermissionRule()matchWildcardPattern()bashPermissions.tsLayer 4路径检查checkPathConstraints()pathValidation.tsLayer 5Sed 命令约束checkSedConstraints()sedValidation.tsLayer 6只读命令自动放行validateReadOnlyCommand()readOnlyValidation.tsLayer 7分类器与用户审批classifyBashCommand()bashPermissions.tsbashClassifier.ts1.1 用户描述的 7 项与源码对照用户描述源码对应是否匹配说明命令验证Layer 1: AST 解析 六道预检查 语义检查✅ 匹配Tree-sitter 解析 控制字符/Unicode/反斜杠/Zsh/花括号预检查 eval/exec/source 语义检查路径检查Layer 4:checkPathConstraints()✅ 匹配35 种 PathCommand 路径提取 --端标志 危险路径检测 cdwrite 组合检测 重定向目标验证Shell 注入bashSecurity.ts23 种安全检查⚠️ 部分匹配Shell 注入检测属于 Layer 1 的组成部分COMMAND_SUBSTITUTION/PROCESS_SUBSTITUTION/IFS_INJECTION 等不是独立的防御层而是 Layer 1 AST 解析的子步骤超时AsyncGenerator 2s 阈值 进度轮询❌ 不属于安全防御层超时是执行管理机制runShellCommand的 AsyncGenerator 流式执行不是权限决策链的一环。源码中不存在超时防御层沙箱shouldUseSandbox.tsSandboxManager⚠️ 部分匹配沙箱决策是 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 -- 处理 危险路径 cdwrite 组合 重定向目标 步骤 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-ClosedAST 解析失败 →too-complex→ askTree-sitter 超时 →PARSE_ABORTED→ ask不降级到 legacy1.4 源码证据AST 解析失败 fail-closedutils/bash/ast.ts// Tree-sitter 解析超时或失败时返回 PARSE_ABORTED不降级到 legacy parserif(parseResultPARSE_ABORTED){return{behavior:ask,reason:parse-aborted};// Fail-Closed}不对称剥离策略bashPermissions.ts// deny 规则激进剥离stripAllLeadingEnvVars — 剥离所有前导环境变量constdenyResultmatchDenyRules(input,stripAllLeadingEnvVars(command));// allow 规则保守剥离stripSafeWrappers — 仅剥离安全的包裹命令constallowResultmatchAllowRules(input,stripSafeWrappers(command));验证项 2Fail-Closed 是否体现在工具默认不可用必须显式注册结论❌ 不是。Fail-Closed 体现在安全属性的默认值选最严格选项而非工具的可用性详细验证2.1 TOOL_DEFAULTS 的实际默认值源码Tool.ts中的TOOL_DEFAULTS对象constTOOL_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()函数exportfunctiongetAllBaseTools():Tools{return[AgentTool,// ← 默认注册BashTool,// ← 默认注册FileReadTool,// ← 默认注册FileEditTool,// ← 默认注册// ... 40 工具默认注册...(isTodoV2Enabled()?[TaskCreateTool,...]:[]),// 条件注册...(isAgentSwarmsEnabled()?[getTeamCreateTool(),...]:[]),]}工具默认在注册表中但通过三种机制过滤isEnabled()feature flag如isTodoV2Enabled()、isAgentSwarmsEnabled()、isBriefEnabled()等Deny 规则过滤filterToolsByDenyRules()在组装时移除被 deny 的工具四维模式过滤Simple 模式仅保留 Bash/Read/EditREPL 模式隐藏原始工具强制走 REPLCoordinator 模式额外暴露 Agent/TaskStop2.4 原书的精准表述原书 4.3.1 节明确指出不对称的代价——一次误判安全可能导致数据丢失而一次误判危险最多让用户多点一次确认按钮。Fail-Closed 的本质是安全属性的默认值选最严格选项而非工具的可用性控制场景默认值Fail-Closed 体现新工具不知道是否并发安全isConcurrencySafe: false→ 串行执行最安全选择新工具不知道是否只读isReadOnly: false→ 触发权限检查最安全选择AST 解析失败too-complex→ ask不放行最安全选择Tree-sitter 超时PARSE_ABORTED→ ask不降级到 legacy最安全选择未知 flagfail closed不 fall-through最安全选择未知 Skill 属性默认需要权限SAFE_SKILL_PROPERTIES白名单外最安全选择2.5 在 30 工具中的广泛体现工具Fail-Closed 场景源码ExitWorktreeToolcountWorktreeChanges()返回 null无法确定状态→ 拒绝删除ExitWorktreeTool.tsExitPlanModeV2Toolauto 模式 circuit breaker → 回退到 default不放行ExitPlanModeV2Tool.tsSkillTool新属性不在SAFE_SKILL_PROPERTIES白名单 → 默认需权限SkillTool.tsSendMessageToolbridge 目标 → bypass-immune即使用户设 bypass 也需询问SendMessageTool.tsCronCreateToolMAX_JOBS 50硬上限 → 超过拒绝CronCreateTool.ts验证项 3工具基类是否统一了 inputSchema/execute/validate 三段式结论✅ 是但实际是四段式inputSchema → validateInput → checkPermissions → call详细验证源码中ToolInput, Output, P泛型接口统一了所有工具的生命周期契约包含 30 方法/属性。核心的执行管线是四段式3.1 四段式管线阶段接口方法职责源码位置Schema 定义inputSchema: ZodType辐射式定义输入参数的运行时验证 schemaTool.ts自定义验证validateInput(input, context): PromiseValidationResult工具特定的业务逻辑验证如 FileEditTool 的 read-before-write 检查Tool.ts权限决策checkPermissions(input, context): PromisePermissionResult工具特定的权限检查默认放行给通用权限系统Tool.ts执行call(args, context, canUseTool, parentMessage, onProgress): PromiseToolResult核心执行逻辑返回包含datacontextModifier的结果Tool.ts3.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返回PermissionResultallow/deny/ask— 三值决策决策空间更大3.4 buildTool() 工厂函数的统一机制exportfunctionbuildToolDextendsAnyToolDef(def:D):BuiltToolD{return{...TOOL_DEFAULTS,// 1. 先铺七个默认值userFacingName:()def.name,// 2. name 作为默认 userFacingName...def,// 3. 开发者定义覆盖}asBuiltToolD}所有 40 工具BashTool、FileEditTool、AgentTool、TodoWriteTool、AskUserQuestionTool、SkillTool…都通过buildTool()创建统一了输入定义inputSchema使用 Zod schemaz.strictObject()/z.object().passthrough()输入验证validateInput可选的自定义验证权限检查checkPermissions可选的工具特定权限逻辑执行逻辑call()核心执行方法结果格式化mapToolResultToToolResultBlockParam结果到 LLM 可读文本的转换3.5 编译期类型安全ToolDef→BuiltToolD的类型体操// 开发者侧7 个默认方法可选typeToolDefInput,Output,POmitToolInput,Output,P,DefaultableToolKeys// 非默认方法必须提供PartialPickToolInput,Output,P,DefaultableToolKeys// 默认方法可选// 调用方侧7 个默认方法必需-? 移除可选性typeBuiltToolDOmitD,DefaultableToolKeys{[KinDefaultableToolKeys]-?:// 所有默认键变为必需KextendskeyofD?undefinedextendsD[K]?ToolDefaults[K]:D[K]:ToolDefaults[K]}这意味着调用方永远不需要 null 检查——tool.checkPermissions一定存在要么是开发者提供的实现要么是TOOL_DEFAULTS的默认值编译期类型系统保证了这一点。3.6 统一契约在各工具中的实例工具inputSchemavalidateInputcheckPermissionscallBashToolz.object({ command, timeout, ... })六道预检查 语义检查bashToolHasPermission()runShellCommand()FileEditToolz.strictObject({ filePath, oldString, newString, ... })read-before-write 检查allow(通用权限系统)原子写入 时间戳校验AgentToolz.object({ description, prompt, subagent_type?, ... })subagent_type 存在性allowrunAgent()ExitWorktreeToolz.object({ action, discard_changes? })worktree session null 检查allowkeep/remove 分支SkillToolz.object({ skill, args? })skill 存在性 type 检查deny/allow 规则 SAFE_SKILL_PROPERTIESinline/forked/remote 三模式SendMessageToolz.object({ to, summary?, message })to 非空 summary 必填 结构化消息不能广播bridge →ask(bypass-immune) / 其他 →allow6 种路由分支3.7 补充工具描述即 Prompt除四段式执行管线外工具还有两个重要的可选但推荐的属性// description 字段 — 本质是写给 LLM 的 Promptdescription:string|AsyncDynamicDescription// mapToolResultToToolResultBlockParam — 工具结果到 LLM 可读文本的转换mapToolResultToToolResultBlockParam:(toolResult:ToolResult,context:ToolContext)PromiseToolResultBlockParam[]这两个属性不参与安全决策但直接影响 LLM 的工具选择行为和结果消费效率属于原书 4.2.2 节工具描述即 Prompt设计模式的实现。总结速查表验证项结论关键发现1. BashTool 七层防御✅ 存在源码实际有10 步检查比原书 7 层更细粒度Shell 注入是 Layer 1 子步骤、超时和输出截断是执行管理机制不属于安全防御链、沙箱是 Layer 7 子步骤2. Fail-Closed❌ 不是默认不可用工具默认在注册表中isEnabled: trueFail-Closed 体现在安全属性默认值isConcurrencySafe: false、isReadOnly: false——“不对称的代价”误判安全可能导致数据丢失误判危险最多多点一次按钮3. 三段式统一✅ 是实际是四段式inputSchema → validateInput → checkPermissions → call通过buildTool()TOOL_DEFAULTSToolDef/BuiltTool编译期类型体操实现运行时防御转移到编译期约束调用方永远不需要 null 检查参考笔记4.1-4.2-tool-registration.md工具注册表 buildTool 泛型接口4.3-bashtool-defense-chain.mdBashTool 七层防御链 10 步检查4.4-file-edit-security.mdFileEditTool 双重时间戳校验4.5-shared-security-logic.md九步执行管线 checkPermissions/validateInput 语义4.6-readonly-tools.md只读工具安全标记4.7-mcp-tool.mdMCP 工具延迟加载4.8-agent-tool.mdAgentTool 子代理执行4.9-ssrf-protection.mdWebFetch SSRF 防护4.10-tool-taxonomy.md30 工具分类 安全属性矩阵 设计模式提炼