111 lines
3.7 KiB
JavaScript
111 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* PreToolUse Hook: Edit/Write 预检派遣器 (v6.2 M2)
|
||
* Matcher: Write|Edit
|
||
*
|
||
* 合并 2 个 PreToolUse 钩子为单进程,减少 ~100ms 串行开销:
|
||
* 1. block-sensitive-files (fail-close, 安全优先)
|
||
* 2. constitution-precheck (fail-close, ERROR 级规则)
|
||
*
|
||
* 读取 stdin 一次,依次调用两个子模块的导出函数。
|
||
* 退出码: 0=放行 | 2=阻断(deny/ask)
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
const path = require('path');
|
||
const readStdin = require('./lib/read-stdin.js');
|
||
|
||
function main() {
|
||
readStdin({ maxSize: 1024 * 1024 }).then(input => {
|
||
// --- 阶段 1: block-sensitive-files (安全优先, fail-close) ---
|
||
// 优先加载 block-sensitive-files.js; 若 disabled (.bak) 则尝试 .bak; 都不存在则跳过
|
||
try {
|
||
let bsf = null;
|
||
try { bsf = require('./block-sensitive-files.js'); } catch {}
|
||
if (!bsf || !bsf.checkFile) {
|
||
try { bsf = require('./block-sensitive-files.js.bak'); } catch {}
|
||
}
|
||
if (bsf && bsf.checkFile) {
|
||
const result = bsf.checkFile(input);
|
||
if (result) {
|
||
process.stderr.write(JSON.stringify({
|
||
hookSpecificOutput: { permissionDecision: result.decision },
|
||
systemMessage: result.message,
|
||
}));
|
||
process.exit(2);
|
||
return;
|
||
}
|
||
}
|
||
// 模块不存在或无 checkFile 导出 → 视为 disabled,跳过
|
||
} catch (e) {
|
||
// block-sensitive-files 加载失败 → fail-close: ask
|
||
process.stderr.write(JSON.stringify({
|
||
hookSpecificOutput: { permissionDecision: 'ask' },
|
||
systemMessage: `[安全防护] block-sensitive-files 模块加载失败(${e.message}),请用户确认是否继续。`,
|
||
}));
|
||
process.exit(2);
|
||
return;
|
||
}
|
||
|
||
// --- 阶段 2: constitution-precheck (ERROR 级违规拦截) ---
|
||
try {
|
||
const { isEnabled } = require('../scripts/feature-flags.js');
|
||
if (!isEnabled('constitution-precheck')) {
|
||
process.exit(0);
|
||
return;
|
||
}
|
||
} catch {}
|
||
|
||
try {
|
||
const cp = require('./constitution-precheck.js');
|
||
if (cp && cp.detectViolation && cp.extractContent && cp.CODE_EXTENSIONS) {
|
||
const fp = input.tool_input?.file_path || '';
|
||
if (fp && cp.CODE_EXTENSIONS.test(fp)) {
|
||
const content = cp.extractContent(input.tool_input);
|
||
if (content) {
|
||
const v = cp.detectViolation(content);
|
||
if (v) {
|
||
// 记录安全事件
|
||
try {
|
||
const { logSecurityEvent } = require('./lib/security-log.js');
|
||
logSecurityEvent('deny', 'constitution-precheck', v.id, fp);
|
||
} catch {}
|
||
|
||
process.stderr.write(JSON.stringify({
|
||
hookSpecificOutput: { permissionDecision: 'deny' },
|
||
systemMessage: [
|
||
'[constitution-precheck] ERROR 级违规,已阻断写入',
|
||
`文件: ${path.basename(fp)}, 行: ${v.line}`,
|
||
`规则: [${v.id}] ${v.label}`,
|
||
'请修复后重试。参考: constitution/AI-CONSTITUTION.md 第十一章',
|
||
].join('\n'),
|
||
}));
|
||
process.exit(2);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch {}
|
||
|
||
// 全部通过,放行
|
||
process.exit(0);
|
||
}).catch((e) => {
|
||
// Fail-close: stdin 解析异常时 ask
|
||
process.stderr.write(JSON.stringify({
|
||
hookSpecificOutput: { permissionDecision: 'ask' },
|
||
systemMessage: `[edit-precheck-dispatcher] 预检异常(${e.message}),请用户确认是否继续。`,
|
||
}));
|
||
process.exit(2);
|
||
});
|
||
}
|
||
|
||
if (typeof module !== 'undefined') {
|
||
module.exports = { main };
|
||
}
|
||
|
||
if (require.main === module) {
|
||
main();
|
||
}
|