#!/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(); }