bookworm-smart-assistant/hooks/constitution-guard.js

203 lines
6.9 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* PostToolUse Hook: 宪法反腐败守卫
* Matcher: Edit|Write
*
* 补充 post-edit-quality-check.js 未覆盖的反腐败模式 (宪法第十一章)
* 专注检测: 隐蔽外联环境探测原型污染文件篡改定时外联
*
* stdin: { tool_name, tool_input: { file_path, content/new_string }, tool_result }
* 退出码: 0 (始终放行, PostToolUse 不阻断, 通过 systemMessage 告警)
*
* Fail-open: 任何异常 exit(0)
*/
const path = require('path');
const readStdin = require('./lib/read-stdin.js');
// ─── Feature Flag 检查 ───────────────────────────────
try {
const { isEnabled } = require('../scripts/feature-flags.js');
if (!isEnabled('constitution-guard')) {
process.exit(0);
}
} catch {
// feature-flags 不存在时默认启用 (新 hook 首次运行)
}
// ─── 宪法反腐败规则 (补充 post-edit-quality-check 未覆盖的) ────
const CODE_EXTENSIONS = /\.(?:js|ts|jsx|tsx|mjs|cjs|mts|cts|py|go|rs|java|rb|php|sh|bash|zsh|ps1|html|svg|xml|yaml|yml|json|toml)$/i;
const CONSTITUTION_RULES = [
// 隐蔽外联 (宪法 11.1)
{
id: 'hidden-network-egress',
label: '疑似隐蔽外联: 新增未知域名的网络请求',
severity: 'error',
pattern: /(?:https?\.request|https?\.get|fetch)\s*\(\s*['"`]https?:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)/,
},
// 原型污染 (宪法 11.1)
{
id: 'prototype-pollution',
label: '原型污染风险: __proto__ 或 constructor.prototype 赋值',
severity: 'error',
pattern: /(?:__proto__|constructor\s*\.\s*prototype)\s*(?:=|\[)/,
},
// 环境探测+外发 (宪法 11.1)
{
id: 'env-probe-exfil',
label: '环境探测: 读取 os.hostname/os.userInfo 可能用于信息收集',
severity: 'warning',
pattern: /os\s*\.\s*(?:hostname|userInfo|networkInterfaces|cpus|platform|arch|release)\s*\(/,
},
// 文件篡改 — package.json scripts (宪法 11.1)
{
id: 'pkg-scripts-tamper',
label: '疑似篡改 package.json scripts 字段',
severity: 'error',
// 检测对 package.json 中 scripts 字段的写入
test: (content, filePath) => {
// [OPT-2] Desktop 临时文件跳过
const _fp = filePath || '';
if (_fp.includes('Desktop') || _fp.includes('desktop')) { return false; }
if (!filePath) return false;
const basename = path.basename(filePath).toLowerCase();
if (basename !== 'package.json') return false;
return /"scripts"\s*:/.test(content);
},
},
// 定时外联 (宪法 11.1)
{
id: 'timed-network-call',
label: '疑似定时外联: setInterval/setTimeout 中包含网络请求',
severity: 'warning',
pattern: /(?:setInterval|setTimeout)\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*\{[^}]*(?:fetch|https?\.request|https?\.get)/,
},
// child_process 含变量拼接 (增强 post-edit-quality-check 的 eval 检测)
{
id: 'exec-injection',
label: '命令注入风险: child_process 参数含变量拼接',
severity: 'error',
pattern: /(?:exec|execSync|execFile|execFileSync|spawn|spawnSync|fork)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+)/,
},
// Base64 混淆执行 (宪法 11.1)
{
id: 'base64-obfuscation',
label: '疑似 Base64 混淆: decode 后可能执行',
severity: 'warning',
pattern: /(?:Buffer\.from|atob)\s*\([^)]+,\s*['"]base64['"]\s*\).*(?:eval|Function|require)/,
},
// .gitignore 篡改
{
id: 'gitignore-tamper',
label: '疑似篡改 .gitignore (可能隐藏恶意文件)',
severity: 'warning',
test: (content, filePath) => {
if (!filePath) return false;
return path.basename(filePath).toLowerCase() === '.gitignore';
},
},
];
/**
* 扫描内容返回匹配的宪法违规
*/
function detectConstitutionViolations(content, filePath) {
if (!content || typeof content !== 'string') return [];
const findings = [];
const lines = content.split('\n');
for (const rule of CONSTITUTION_RULES) {
// 自定义 test 函数
if (rule.test) {
if (rule.test(content, filePath)) {
findings.push({ id: rule.id, label: rule.label, severity: rule.severity, line: 0 });
}
continue;
}
// 正则逐行匹配
if (rule.pattern) {
for (let i = 0; i < lines.length; i++) {
if (rule.pattern.test(lines[i])) {
findings.push({ id: rule.id, label: rule.label, severity: rule.severity, line: i + 1 });
break; // 每条规则只报一次
}
}
}
}
return findings;
}
/**
* tool_input 中提取内容
*/
function extractContent(input) {
if (input.tool_input?.content) return input.tool_input.content;
if (input.tool_input?.new_string) return input.tool_input.new_string;
return null;
}
// ─── 主流程 ──────────────────────────────────────────
function main() {
readStdin({ maxSize: 256 * 1024 }).then(input => {
const filePath = input.tool_input?.file_path;
if (!filePath) {
process.exit(0);
return;
}
// 代码文件 + package.json + .gitignore
const isCodeFile = CODE_EXTENSIONS.test(filePath);
const isSpecialFile = /(?:package\.json|\.gitignore|\.env)$/i.test(filePath);
if (!isCodeFile && !isSpecialFile) {
process.exit(0);
return;
}
const content = extractContent(input);
if (!content) {
process.exit(0);
return;
}
const findings = detectConstitutionViolations(content, filePath);
if (findings.length > 0) {
const errors = findings.filter(f => f.severity === 'error');
const warnings = findings.filter(f => f.severity === 'warning');
const parts = [];
if (errors.length > 0) {
parts.push(errors.map(f => ` [ERROR][${f.id}] ${f.line ? 'L' + f.line + ': ' : ''}${f.label}`).join('\n'));
}
if (warnings.length > 0) {
parts.push(warnings.map(f => ` [WARN][${f.id}] ${f.line ? 'L' + f.line + ': ' : ''}${f.label}`).join('\n'));
}
const severity = errors.length > 0 ? 'ERROR' : 'WARN';
const result = {
continue: true,
systemMessage: `[constitution-guard] (${path.basename(filePath)}) ${severity}:\n${parts.join('\n')}\n\n${errors.length > 0 ? '请检查以上 error 级别问题是否为误报,若非误报必须修复。参考: constitution/AI-CONSTITUTION.md 第十一章' : '(仅提醒,不阻断)'}`,
};
process.stdout.write(JSON.stringify(result));
}
process.exit(0);
}).catch(() => process.exit(0));
}
// 模块导出 (供测试)
if (typeof module !== 'undefined') {
module.exports = { CONSTITUTION_RULES, detectConstitutionViolations, extractContent };
}
if (require.main === module) {
main();
}