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);
|
||
});
|