一、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 可以配置在两个层级:
- 全局配置:位于用户主目录的 .claude/settings.json,对所有项目生效。
- 项目级配置:位于项目根目录的 .claude/settings.json,仅对当前项目生效。
两个层级的 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 | 文件存在性、路径合法性 | 空路径、特殊字符路径、目录路径 |
| Edit | old_string 唯一性、参数完整性 | 空字符串、全局匹配歧义 |
| Write | 路径可写性、内容编码 | 二进制内容、超大文件 |
| Bash | 命令安全性、超时设置 | 交互式命令、无限循环 |
| Glob | pattern 合法性、范围限制 | ReDoS 模式、过于宽泛的匹配 |
| WebFetch | URL 格式、域名白名单 | 内网地址、非 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 固定窗口:滑动时间窗口算法比固定窗口更精确——它基于实际调用时间戳计算频率,而不是简单地按分钟重置计数器。这避免了在窗口边界处出现调用峰值(即"踩点"问题)。上面的实现采用了滑动窗口方案。
针对不同工具的差异化频率阈值
不同工具的"成本"和"风险"差异很大,应当设置不同的频率限制:
| 工具 | 频率限制 | 限制原因 |
| Read | 30次/分钟 | 读取操作开销低,可适当放宽 |
| Edit | 10次/分钟 | 修改操作需谨慎,限制调用频率 |
| Write | 10次/分钟 | 写入操作同样需要限制 |
| Bash | 20次/分钟 | 命令执行消耗资源,需控制 |
| WebFetch | 5次/分钟 | 网络请求慢且可能触发反爬 |
| Glob | 15次/分钟 | 文件系统遍历可能影响性能 |
防止无限循环调用
频率限制最重要的价值之一是防止无限循环。当 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 的输出应包含足够详细的错误信息,帮助用户快速理解被阻断的原因并进行调整。