124 lines
4.2 KiB
JavaScript
124 lines
4.2 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* PreToolUse:Bash 合并调度器
|
|||
|
|
* 合并 block-dangerous-commands + code-quality-gate + commit-message-lint
|
|||
|
|
* 减少 3 次 Node.js 进程启动为 1 次 (节省 ~80-100ms)
|
|||
|
|
*
|
|||
|
|
* 优先级:
|
|||
|
|
* 1. block-dangerous-commands (安全, fail-close)
|
|||
|
|
* 2. code-quality-gate (构建追踪, fail-open)
|
|||
|
|
* 3. commit-message-lint (commit 规范, fail-open)
|
|||
|
|
*
|
|||
|
|
* 退出码: 0=放行, 2=阻断/ask (stderr 输出 JSON)
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const readStdin = require('./lib/read-stdin.js');
|
|||
|
|
|
|||
|
|
readStdin({ maxSize: 1024 * 1024 }).then(input => {
|
|||
|
|
const command = (input.tool_input && input.tool_input.command) || '';
|
|||
|
|
if (!command) {
|
|||
|
|
process.exit(0);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 1. block-dangerous-commands (最高优先级, fail-close) ───
|
|||
|
|
// 安全检查: 异常时 ask (fail-closed)
|
|||
|
|
try {
|
|||
|
|
const bdc = require('./block-dangerous-commands.js');
|
|||
|
|
if (bdc.checkCommand) {
|
|||
|
|
const result = bdc.checkCommand(command, input);
|
|||
|
|
if (result) {
|
|||
|
|
// B5: CLAUDE_SAFETY_MODE=strict 时 ask 升级为 deny
|
|||
|
|
let decision = result.decision; // 'deny' 或 'ask'
|
|||
|
|
if (decision === 'ask' && process.env.CLAUDE_SAFETY_MODE === 'strict') {
|
|||
|
|
decision = 'deny';
|
|||
|
|
result.message += '\n(CLAUDE_SAFETY_MODE=strict: ask 已升级为 deny)';
|
|||
|
|
}
|
|||
|
|
const output = {
|
|||
|
|
hookSpecificOutput: {
|
|||
|
|
permissionDecision: decision,
|
|||
|
|
},
|
|||
|
|
systemMessage: result.message,
|
|||
|
|
};
|
|||
|
|
process.stderr.write(JSON.stringify(output));
|
|||
|
|
process.exit(2);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
// fail-closed: 安全模块异常时请求用户确认 (strict 模式下直接 deny)
|
|||
|
|
const failDecision = process.env.CLAUDE_SAFETY_MODE === 'strict' ? 'deny' : 'ask';
|
|||
|
|
const failSuffix = failDecision === 'deny' ? '\n(CLAUDE_SAFETY_MODE=strict: 异常时直接拒绝)' : '';
|
|||
|
|
process.stderr.write(JSON.stringify({
|
|||
|
|
hookSpecificOutput: { permissionDecision: failDecision },
|
|||
|
|
systemMessage: `[安全防护] 命令检查模块异常(${(e.message || '').slice(0, 80)}),${failDecision === 'deny' ? '已拒绝执行' : '请用户确认是否执行'}。${failSuffix}`,
|
|||
|
|
}));
|
|||
|
|
process.exit(2);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 2. code-quality-gate (构建命令, fail-open) ───
|
|||
|
|
try {
|
|||
|
|
const cqg = require('./code-quality-gate.js');
|
|||
|
|
if (cqg.checkBuild) {
|
|||
|
|
const result = cqg.checkBuild(command, input);
|
|||
|
|
if (result) {
|
|||
|
|
if (result.decision === 'deny') {
|
|||
|
|
// enforce 模式阻断
|
|||
|
|
process.stderr.write(JSON.stringify({
|
|||
|
|
hookSpecificOutput: { permissionDecision: 'deny' },
|
|||
|
|
systemMessage: result.message,
|
|||
|
|
}));
|
|||
|
|
process.exit(2);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (result.decision === 'warn') {
|
|||
|
|
// warn 模式: 放行但附带提醒,继续后续检查
|
|||
|
|
// (warn 消息将与后续检查结果合并)
|
|||
|
|
const output = {
|
|||
|
|
continue: true,
|
|||
|
|
systemMessage: result.message,
|
|||
|
|
};
|
|||
|
|
process.stdout.write(JSON.stringify(output));
|
|||
|
|
process.exit(0);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
// fail-open: 构建检查异常时静默放行
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 3. commit-message-lint (git commit, fail-open) ───
|
|||
|
|
try {
|
|||
|
|
const cml = require('./commit-message-lint.js');
|
|||
|
|
if (cml.checkCommit) {
|
|||
|
|
const result = cml.checkCommit(command, input);
|
|||
|
|
if (result) {
|
|||
|
|
process.stderr.write(JSON.stringify({
|
|||
|
|
hookSpecificOutput: {
|
|||
|
|
permissionDecision: result.decision, // 'ask'
|
|||
|
|
},
|
|||
|
|
systemMessage: result.message,
|
|||
|
|
}));
|
|||
|
|
process.exit(2);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
// fail-open: commit-lint 异常时静默放行
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 全部通过 → 放行
|
|||
|
|
process.exit(0);
|
|||
|
|
}).catch(() => {
|
|||
|
|
// stdin 读取/解析失败 → fail-closed (strict 模式下升级为 deny)
|
|||
|
|
const isStrict = process.env.CLAUDE_SAFETY_MODE === 'strict';
|
|||
|
|
const decision = isStrict ? 'deny' : 'ask';
|
|||
|
|
process.stderr.write(JSON.stringify({
|
|||
|
|
hookSpecificOutput: { permissionDecision: decision },
|
|||
|
|
systemMessage: `[bash-precheck-dispatcher] stdin 解析异常,${isStrict ? '已拒绝执行' : '请用户确认是否执行'}。`,
|
|||
|
|
}));
|
|||
|
|
process.exit(2);
|
|||
|
|
});
|