工具调用前Hook实战(before:tool)

工具调用前的自动化处理

一、before:tool Hook概述

before:tool 是 Claude Code 提供的一种前置拦截机制,在每次工具(Tool)调用正式执行之前触发。它可以对即将执行的调用进行检查、修改、记录甚至阻断,是实现安全管控、数据校验和流程自动化的核心扩展点。

与 after:tool(工具调用后触发)不同,before:tool 拥有阻止调用发生的特权。如果 before:tool Hook 返回了特定的阻断信号,对应的工具调用将不会被执行,从而有效避免误操作、权限越界或资源滥用。

可阻断性
在工具调用真正发生之前拦截,可以完全阻止不合规的调用,防止对系统造成损害。
工具感知
通过 $TOOL_NAME 获取当前正在被调用的工具名称,实现针对不同工具的差异化逻辑。
参数访问
通过 $TOOL_ARGS 获取工具调用的完整参数列表,对参数内容进行细粒度检查和校验。
审计能力
可将每次工具调用记录到审计日志中,形成完整的操作追踪链路,方便事后审查。
核心机制:before:tool Hook 按配置顺序依次执行。如果某个 Hook 返回了阻断指令,后续 Hook 和实际的工具调用都将被跳过。每个 Hook 的执行结果通过标准输出或返回码传递给运行时,Claude Code 根据响应决定是否继续执行。

基础配置结构

before:tool Hook 在 settings.json 中配置,属于 hooks 配置块下的顶层 Hook。以下是一个最简配置示例:

{ "hooks": { "before:tool": { "command": "node scripts/validate-tool.js", "timeout": 5000 } } }

在上面的配置中,每次工具调用前都会执行 validate-tool.js 脚本。如果脚本退出码为 0,则调用继续;如果退出码非 0,则调用被阻断。timeout 参数控制脚本的最大执行时间,超过时限则视为超时阻断。

全局与项目级配置

before:tool Hook 可以配置在两个层级:

两个层级的 Hook 会合并执行,项目级配置优先于全局配置。这意味着你可以为特定项目设置更严格的校验规则,而不会影响其他项目的使用。

核心要点:before:tool Hook 是 Claude Code 安全体系的第一道防线。它让开发者可以在工具调用的关键路径上插入任意自定义逻辑,从简单的日志记录到复杂的权限校验都可以实现。理解这一机制是掌握 Claude Code 扩展能力的基础。

二、文件操作权限检查Hook

文件读写是最常见的工具调用操作之一。Read、Edit、Write、Glob 等工具都需要对文件系统进行访问。如果不对这些操作加以限制,就可能导致敏感文件泄露、配置文件被意外修改甚至系统文件被破坏。通过 before:tool Hook,可以实现精细化的文件操作权限控制。

路径白名单校验

核心思路是维护一个允许访问的目录白名单,任何试图访问白名单之外路径的操作都将被阻断。以下是一个基于 Node.js 的路径校验 Hook 示例:

// file-permission-hook.js const path = require('path'); const ALLOWED_PATHS = [ path.resolve(__dirname, '../src'), path.resolve(__dirname, '../docs'), path.resolve(__dirname, '../public') ]; const toolName = process.env.TOOL_NAME; const args = JSON.parse(process.env.TOOL_ARGS || '{}'); // 只检查文件操作类工具 if (['Read', 'Edit', 'Write'].includes(toolName)) { const filePath = args.file_path || args.path; if (!filePath) { console.error('阻断:缺少文件路径参数'); process.exit(1); } const resolved = path.resolve(filePath); const allowed = ALLOWED_PATHS.some(dir => resolved.startsWith(dir)); if (!allowed) { console.error(`阻断:路径 ${filePath} 不在允许的目录范围`); process.exit(1); } } process.exit(0);

路径穿越攻击检测

路径穿越(Path Traversal)是一种常见的攻击手段,攻击者通过 ../ 序列跳出限制目录。Hook 需要对所有文件路径进行规范化处理,检测并阻断包含路径穿越序列的请求:

// 路径穿越检测逻辑 function detectPathTraversal(filePath) { // 将路径解析为规范形式 const normalized = path.normalize(filePath); // 检查是否包含连续的 .. 序列 const traversalPattern = /(\.\.[\\/]){2,}/; if (traversalPattern.test(filePath) || traversalPattern.test(normalized)) { return true; } // 检查最终路径是否跳出项目根目录 const projectRoot = path.resolve(__dirname, '..'); if (!normalized.startsWith(projectRoot)) { return true; } return false; }

保护关键配置文件

某些配置文件(如 .env、settings.json、密钥文件等)即使位于白名单目录内,也应当受到额外保护,不允许被修改操作触及:

const PROTECTED_FILES = [ '.env', '.env.local', '.env.production', 'settings.json', 'settings.local.json', 'CLAUDE.md', '.claude/settings.json', '**/credentials.json', '**/secrets/**' ]; function isProtectedFile(filePath) { const filename = path.basename(filePath); if (PROTECTED_FILES.includes(filename)) return true; // 支持 glob 模式匹配 if (filePath.includes('credentials.json') || filePath.includes('secrets/') || filePath.includes('.env')) { return true; } return false; }
安全原则:永远不要信任用户提供的路径参数。即使路径经过了白名单校验,也应当进行规范化处理和二次检查。安全审计日志应记录每次阻断事件,包括时间戳、工具名称、目标路径和阻断原因,以便事后分析和追溯。

越权访问审计日志

所有被阻断的越权访问尝试应当记录到审计日志中,形成完整的操作轨迹:

// 审计日志格式 { "timestamp": "2026-05-08T10:00:00.000Z", "event": "ACCESS_DENIED", "tool": "Read", "target": "/etc/passwd", "reason": "路径不在白名单中", "user": "claude-agent", "action": "BLOCKED" }

核心要点:文件操作权限检查是 before:tool Hook 最典型也最重要的应用场景。通过组合白名单校验、路径穿越检测、关键文件保护和审计日志四个层面的防护,可以构建一个纵深防御体系,有效防止文件系统层面的安全风险。

三、命令执行审批Hook

Bash 工具是 Claude Code 中能力最强的工具之一,它允许直接执行 Shell 命令。正因为强大,它也带来了极高的风险。一个不经意的 rm -rf / 或 curl | bash 可能导致灾难性后果。通过 before:tool Hook,我们可以对 Bash 命令进行审批和过滤。

危险命令检测与阻断

以下 Hook 会检测 Bash 工具即将执行的命令字符串,识别并阻断高危操作:

// command-approval-hook.js const DANGEROUS_COMMANDS = [ { pattern: /^rm\s+-rf\s+\/?$/m, level: 'critical', msg: '删除根目录' }, { pattern: /^rm\s+-rf\s+\/\s*$/m, level: 'critical', msg: '删除根目录' }, { pattern: /^curl\s+.*\||^curl\s+.*\|/, level: 'high', msg: '远程脚本管道执行' }, { pattern: /^wget\s+.*-O\s*-\s*\|/, level: 'high', msg: '远程脚本管道执行' }, { pattern: /^sudo\s+/m, level: 'high', msg: '使用sudo提升权限' }, { pattern: /^chmod\s+-R\s+777\s+\//m, level: 'critical', msg: '修改根目录权限' }, { pattern: /^dd\s+if=\/dev\/zero\s+of=\/dev/, level: 'critical', msg: '覆写块设备' }, { pattern: /^>\s*\/proc\/sys\//m, level: 'critical', msg: '修改内核参数' }, { pattern: /^:\(\)\{\s*:\|:&\s*\};:/, level: 'critical', msg: 'Fork炸弹' } ]; if (toolName === 'Bash') { const command = args.command || ''; for (const rule of DANGEROUS_COMMANDS) { if (rule.pattern.test(command)) { console.error(`阻断:检测到高危命令 [${rule.level}] - ${rule.msg}`); console.error(`命令: ${command.substring(0, 200)}`); process.exit(1); } } }
工作原理:Hook 脚本通过环境变量获取即将执行的命令字符串,然后与预定义的危险模式规则表进行匹配。匹配到高危模式时,脚本以非零退出码退出,Claude Code 识别到阻断信号后取消该次工具调用。同时,阻断事件会被记录到审计系统中。

用户确认交互流程

对于潜在风险但并非绝对危险的操作(如删除特定文件、执行修改等),可以实现"需要用户确认才能继续"的交互模式:

// 交互式确认流程 const MODERATE_RISK_PATTERNS = [ { pattern: /rm\s+/, level: 'moderate', msg: '执行删除操作' }, { pattern: /drop\s+table/i, level: 'moderate', msg: '执行数据库删除操作' }, { pattern: /git\s+push\s+--force/, level: 'moderate', msg: '强制推送Git提交' }, { pattern: /npm\s+publish/, level: 'moderate', msg: '发布NPM包' }, { pattern: />.*\.(log|out)/, level: 'low', msg: '清空输出文件' } ]; // 匹配到中风险命令时,输出审批请求 // 父进程读取输出后决定是否放行 if (matchedModerate) { console.log(`[APPROVAL_REQUIRED] 操作: ${rule.msg}`); console.log(`[APPROVAL_REQUIRED] 命令: ${command}`); console.log(`[APPROVAL_REQUIRED] 默认策略: 阻断,等待用户确认`); process.exit(2); // 特殊退出码表示需要用户确认 }
最佳实践:对于高风险操作建议直接阻断,对于中风险操作提示用户确认,对于低风险操作仅记录日志。不同项目可以根据自身的安全需求调整风险等级阈值。在 CI/CD 等自动化环境中,建议将所有非零风险的命令全部阻断,确保流水线安全。

审计日志记录

每次命令执行(无论是放行还是阻断)都应当记录审计日志,包含完整的上下文信息:

// 命令执行审计日志条目 { "timestamp": "2026-05-08T10:05:00.000Z", "event": "COMMAND_AUDIT", "tool": "Bash", "command": "rm -rf ./node_modules", "risk_level": "moderate", "status": "APPROVED", // APPROVED | BLOCKED | PENDING_CONFIRM "approved_by": "user", // "auto" | "user" | "hook" "execution_time_ms": 1234, "exit_code": 0 }

核心要点:命令执行审批Hook的核心价值在于"预防胜于补救"。与其在发生事故后恢复数据,不如在命令执行前就将其阻断。通过合理配置风险规则表和审批流程,可以在安全性和便利性之间取得平衡。

四、参数有效性验证Hook

工具调用的参数质量直接影响执行结果的正确性和安全性。无效的参数可能导致工具执行失败、产生错误输出甚至触发未定义行为。通过 before:tool Hook 对参数进行前置校验,可以将错误拦截在萌芽状态。

参数格式与范围检查

每种工具都有自己特定的参数规范。以下示例展示了如何针对不同工具的参数进行格式校验:

// parameter-validation-hook.js const validators = { // Read 工具:file_path 不能为空,且应为合法路径 Read: (args) => { if (!args.file_path || args.file_path.trim() === '') { return { valid: false, error: 'file_path 参数不能为空' }; } if (args.file_path.length > 1024) { return { valid: false, error: 'file_path 长度超出限制 (max: 1024)' }; } return { valid: true }; }, // Edit 工具:old_string 和 new_string 不能同时为空 Edit: (args) => { if (!args.file_path) { return { valid: false, error: 'Edit 操作必须指定 file_path' }; } if (!args.old_string && !args.new_string) { return { valid: false, error: 'old_string 和 new_string 不能同时为空' }; } if (args.old_string === args.new_string) { return { valid: false, error: 'old_string 和 new_string 不能相同' }; } return { valid: true }; }, // Glob 工具:pattern 参数必须合法 Glob: (args) => { if (!args.pattern) { return { valid: false, error: 'pattern 参数不能为空' }; } // 防止 ReDoS 攻击 if (args.pattern.length > 200) { return { valid: false, error: 'pattern 过长,可能导致性能问题' }; } return { valid: true }; } }; const toolName = process.env.TOOL_NAME; const args = JSON.parse(process.env.TOOL_ARGS || '{}'); const validator = validators[toolName]; if (validator) { const result = validator(args); if (!result.valid) { console.error(`参数校验失败: ${result.error}`); process.exit(1); } } process.exit(0);

数据库查询参数注入检测

当工具调用涉及数据库操作时,参数中可能包含 SQL 注入攻击向量。before:tool Hook 可以在参数传递到数据库之前进行检测:

// SQL 注入检测 function detectSQLInjection(params) { const sqlInjectionPatterns = [ /'?\s*OR\s+['"]?\s*1\s*=\s*1\s*['"]?/i, /'?\s*OR\s+['"]?\s*1\s*=\s*1\s*--/i, /UNION\s+ALL\s+SELECT/i, /DROP\s+TABLE/i, /DELETE\s+FROM/i, /INSERT\s+INTO/i, /xp_cmdshell/i, /WAITFOR\s+DELAY/i, /BENCHMARK\s*\(/i ]; for (const [key, value] of Object.entries(params)) { if (typeof value === 'string') { for (const pattern of sqlInjectionPatterns) { if (pattern.test(value)) { return { detected: true, parameter: key, value: value.substring(0, 100) }; } } } } return { detected: false }; }

URL与路径格式验证

对于接受 URL 或路径参数的 WebFetch、Read 等工具,验证其格式的正确性可以有效避免无效请求:

// URL 格式验证 function validateURL(url) { try { const parsed = new URL(url); // 只允许 HTTP/HTTPS 协议 if (!['http:', 'https:'].includes(parsed.protocol)) { return { valid: false, error: '只允许 HTTP/HTTPS 协议' }; } // 阻止内网地址 const hostname = parsed.hostname; if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('10.') || hostname.startsWith('172.') || hostname.startsWith('192.168.')) { return { valid: false, error: '不允许访问内网地址' }; } return { valid: true }; } catch { return { valid: false, error: 'URL 格式不合法' }; } }
工具名称校验重点常见无效参数
Read文件存在性、路径合法性空路径、特殊字符路径、目录路径
Editold_string 唯一性、参数完整性空字符串、全局匹配歧义
Write路径可写性、内容编码二进制内容、超大文件
Bash命令安全性、超时设置交互式命令、无限循环
Globpattern 合法性、范围限制ReDoS 模式、过于宽泛的匹配
WebFetchURL 格式、域名白名单内网地址、非 HTTP 协议

核心要点:参数有效性验证是在错误发生之前进行拦截的最有效手段。通过为每种工具定制专门的验证规则,可以确保传递给工具的每一个参数都是格式正确、语义合法、安全无害的。

五、工具调用频率限制Hook

在自动化流程中,工具可能会被高频重复调用。如果没有频率限制机制,可能引发无限循环、API 配额耗尽、资源被过度占用等问题。before:tool Hook 是实现工具调用频率限制的理想位置。

基于时间窗口的限频策略

以下示例实现了一个基于滑动时间窗口的频率限制器,限制同一工具在单位时间内的调用次数:

// rate-limiter-hook.js const fs = require('fs'); const path = require('path'); const RATE_LIMIT_FILE = path.join(__dirname, '.rate-limit-state.json'); // 限频配置:每种工具每分钟允许的最大调用次数 const TOOL_LIMITS = { Read: 30, // 每分钟最多30次 Edit: 10, // 每分钟最多10次 Write: 10, // 每分钟最多10次 Bash: 20, // 每分钟最多20次 Glob: 15, // 每分钟最多15次 WebFetch: 5, // 每分钟最多5次 Grep: 15, // 每分钟最多15次 default: 20 // 默认限制 }; // 读取状态文件 let state = { timestamps: {} }; try { state = JSON.parse(fs.readFileSync(RATE_LIMIT_FILE, 'utf8')); } catch { /* 文件不存在则使用默认状态 */ } const toolName = process.env.TOOL_NAME; const now = Date.now(); const WINDOW_MS = 60000; // 1分钟时间窗口 // 初始化时间戳数组 if (!state.timestamps[toolName]) { state.timestamps[toolName] = []; } // 清理过期记录 state.timestamps[toolName] = state.timestamps[toolName] .filter(ts => now - ts < WINDOW_MS); // 检查是否超过限制 const limit = TOOL_LIMITS[toolName] || TOOL_LIMITS.default; if (state.timestamps[toolName].length >= limit) { const oldest = state.timestamps[toolName][0]; const waitMs = WINDOW_MS - (now - oldest); console.error(`频率限制:工具 ${toolName} 已达到上限 ` + `(${limit}/${WINDOW_MS/1000}s),请等待 ${Math.ceil(waitMs/1000)} 秒后重试`); process.exit(1); } // 记录本次调用 state.timestamps[toolName].push(now); // 写回状态 fs.writeFileSync(RATE_LIMIT_FILE, JSON.stringify(state), 'utf8'); process.exit(0);
滑动窗口 vs 固定窗口:滑动时间窗口算法比固定窗口更精确——它基于实际调用时间戳计算频率,而不是简单地按分钟重置计数器。这避免了在窗口边界处出现调用峰值(即"踩点"问题)。上面的实现采用了滑动窗口方案。

针对不同工具的差异化频率阈值

不同工具的"成本"和"风险"差异很大,应当设置不同的频率限制:

工具频率限制限制原因
Read30次/分钟读取操作开销低,可适当放宽
Edit10次/分钟修改操作需谨慎,限制调用频率
Write10次/分钟写入操作同样需要限制
Bash20次/分钟命令执行消耗资源,需控制
WebFetch5次/分钟网络请求慢且可能触发反爬
Glob15次/分钟文件系统遍历可能影响性能

防止无限循环调用

频率限制最重要的价值之一是防止无限循环。当 Claude 在某个任务中反复调用同一工具时,频率限制会在达到阈值时强制中断循环:

// 无限循环检测增强逻辑 // 结合调用频率和内容相似度检测 // 检测连续相同的调用 if (state.timestamps[toolName].length >= 5) { const recentCalls = state.timestamps[toolName].slice(-5); const argsHash = hashFunction(process.env.TOOL_ARGS); // 如果最后5次调用参数完全相同,且间隔小于2秒 const allSame = recentCalls.every(c => c.argsHash === argsHash); const allFast = Math.abs(recentCalls[4].time - recentCalls[0].time) < 10000; if (allSame && allFast) { console.error('检测到可能的无限循环:相同参数在短时间内被重复调用'); process.exit(1); } }
注意事项:频率限制状态文件(.rate-limit-state.json)需要妥善管理。如果状态文件被删除,频率计数器将重置,可能导致突发高频调用。建议将状态文件放置在持久化存储中,并考虑添加文件锁以防止并发写入冲突。

延迟替代方案

除了直接阻断调用,频率限制还可以采用"延迟执行"策略——当调用频率接近阈值时,自动增加延迟时间:

// 自适应延迟策略 const usage = state.timestamps[toolName].length; const ratio = usage / limit; // 当前使用率 if (ratio > 0.8) { // 使用率超过80%,引入延迟 const delayMs = Math.round(2000 * (ratio - 0.8) / 0.2); console.log(`[RATE_LIMIT_DELAY] 工具 ${toolName} 使用率 ${Math.round(ratio*100)}%,` + `自动延迟 ${delayMs}ms`); // 通过输出指令让主程序等待 process.exit(10); // 特殊退出码表示需要延迟 }

核心要点:频率限制看似是一个简单的计数机制,但它的设计需要综合考虑滑动窗口算法、差异化限频策略、循环检测以及优雅的降级方案。一个设计良好的频率限制器不仅能保护系统资源,还能提升整体调用的稳定性和可预测性。

六、实战综合配置示例

将前面五个模块整合到一个完整的 before:tool Hook 配置中,形成一个多层防护体系:

{ "hooks": { "before:tool": [ { "command": "node hooks/file-permission.js", "description": "文件权限检查 - 白名单与路径穿越检测", "timeout": 3000 }, { "command": "node hooks/command-approval.js", "description": "命令审批 - 检测并阻断高危Shell命令", "timeout": 3000 }, { "command": "node hooks/parameter-validation.js", "description": "参数校验 - 检查工具调用参数格式和范围", "timeout": 2000 }, { "command": "node hooks/rate-limiter.js", "description": "频率限制 - 基于滑动窗口的调用频率控制", "timeout": 1000 } ] } }
执行顺序说明:Hook 按配置数组中的顺序依次执行。建议按"安全检查优先"的原则排序——先做最严格的权限检查,再做参数校验,最后做频率限制。这样可以在最早期阶段阻断绝大多数不合规的调用,避免后续环节的无效处理。

多层防护效果评估

防护层级阻断场景误报率性能开销
文件权限检查越权读取、写入敏感文件~2ms
命令审批rm -rf /, curl | bash 等高危命令中(可能阻断合法命令)~1ms
参数校验无效路径、SQL注入、畸形参数~1ms
频率限制无限循环、API滥用、资源耗尽~1ms
最佳实践:before:tool Hook 的设计应遵循"白名单优先"原则——默认拒绝所有操作,仅放行明确允许的调用。这与"默认允许、按需拒绝"的模式相比,可以提供更强的安全保障。同时,每个 Hook 的输出应包含足够详细的错误信息,帮助用户快速理解被阻断的原因并进行调整。