#!/usr/bin/env node /** * patch-pipeline-hooks-fix-v1.js · Phase α 冲刺 3 修复 · 2026-04-25 * * 修 3 个 E2E 发现的 bug: * * Bug 1 [HIGH 安全]: staging-validator entropy 阈值 4.5 对 hex 字符串无效 * log2(16)=4.0,阈值 4.5 永远挂不到 hex API key * 修法: 阈值 4.5 → 3.5 (hex 会命中); 但 alpha+num+symbol 仍需更高区分度 * 额外对 hex32+/base64 64+ 特定形态加专门 regex * * Bug 2 [LOW 元数据]: post-edit-snapshot skip-oversize 事件缺 sessionId * 修法: appendManifest 时附 sessionId * * Bug 3 [MED 安全覆盖]: credential-patterns 仅 6 条命令行规则,不够 staging 扫描用 * 修法: 加 4 条常见生产凭证格式 (sk_live_/sk_test_/ghs_/hex32+) * * 幂等: 检测 sentinel 跳过; 原子 tmp+rename; .bak 保留 */ 'use strict'; const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const ROOT = path.resolve(__dirname, '..', '..'); const SNAPSHOT = path.join(ROOT, 'hooks', 'post-edit-snapshot.js'); const VALIDATOR = path.join(ROOT, 'hooks', 'staging-validator.js'); const CRED_PATTERNS = path.join(ROOT, 'hooks', 'rules', 'credential-patterns.json'); const SENTINEL = '/* fix-v1-applied */'; function backup(p) { const bak = p + '.bak.fix-v1.' + new Date().toISOString().replace(/[:.]/g, '-'); fs.copyFileSync(p, bak); return path.basename(bak); } function atomicWrite(p, content) { const tmp = p + '.tmp.' + process.pid; fs.writeFileSync(tmp, content); fs.renameSync(tmp, p); } // ========= Bug 1 & 3: staging-validator + credential-patterns ========= function fixValidator() { const src = fs.readFileSync(VALIDATOR, 'utf8'); if (src.includes(SENTINEL)) { console.log('[fix-v1] validator 已打过补丁, 跳过'); return { changed: false }; } const newSrc = src .replace( /shannonEntropy\(t\) > 4\.5/, 'shannonEntropy(t) > 3.5 ' + SENTINEL ) .replace( // 额外加一个硬 regex 覆盖 hex32+ /const highEntropy = \(text\.match\(\/\\b\[A-Za-z0-9_\\-\]\{32,\}\\b\/g\) \|\| \[\]\)/, 'const hexHits = (text.match(/\\b[a-f0-9]{32,}\\b/gi) || []).slice(0, 2);\n' + ' if (hexHits.length > 0) hits.push(\'hex32+-suspicious\');\n' + ' const highEntropy = (text.match(/\\b[A-Za-z0-9_\\-]{32,}\\b/g) || [])' ); if (newSrc === src) { console.log('[fix-v1] validator 未找到预期锚点, 跳过 (可能已是不同版本)'); return { changed: false }; } const bak = backup(VALIDATOR); atomicWrite(VALIDATOR, newSrc); console.log('[fix-v1] validator entropy 4.5→3.5 + hex32+ regex 已加 (bak:', bak + ')'); return { changed: true }; } function fixSnapshot() { const src = fs.readFileSync(SNAPSHOT, 'utf8'); if (src.includes('skip-oversize') && src.includes("event: 'skip-oversize'") && src.includes('sessionId,\n size: stat.size')) { // already has sessionId nearby } // 检查是否已修 if (src.includes("event: 'skip-oversize', sessionId")) { console.log('[fix-v1] snapshot 已打过补丁, 跳过'); return { changed: false }; } // 在 skip-oversize 处插入 sessionId const oldPattern = /event: 'skip-oversize', originalPath: filePath, size: stat\.size, cap: MAX_FILE_BYTES/; const newStr = "event: 'skip-oversize', sessionId: getSessionId(input), originalPath: filePath, size: stat.size, cap: MAX_FILE_BYTES"; if (!oldPattern.test(src)) { console.log('[fix-v1] snapshot 未找到预期锚点, 跳过'); return { changed: false }; } const newSrc = src.replace(oldPattern, newStr); const bak = backup(SNAPSHOT); atomicWrite(SNAPSHOT, newSrc); console.log('[fix-v1] snapshot skip-oversize 已加 sessionId (bak:', bak + ')'); return { changed: true }; } function fixCredPatterns() { const raw = fs.readFileSync(CRED_PATTERNS, 'utf8'); const json = JSON.parse(raw); const existing = new Set(json.patterns.map(p => p.regex)); const newRules = [ { regex: 'sk_live_[A-Za-z0-9]{24,}', flags: '', reason: 'Stripe Live Secret Key (文件内容)' }, { regex: 'sk_test_[A-Za-z0-9]{24,}', flags: '', reason: 'Stripe Test Secret Key (文件内容)' }, { regex: 'ghp_[A-Za-z0-9]{36,}', flags: '', reason: 'GitHub Personal Access Token (新版)' }, { regex: 'xox[baprs]-[A-Za-z0-9-]{10,}', flags: '', reason: 'Slack Token' }, ]; const toAdd = newRules.filter(r => !existing.has(r.regex)); if (toAdd.length === 0) { console.log('[fix-v1] credential-patterns 已含新规则, 跳过'); return { changed: false }; } const bak = backup(CRED_PATTERNS); json._version = 'v3.9-staging-ext'; json.patterns.push(...toAdd); atomicWrite(CRED_PATTERNS, JSON.stringify(json, null, 2) + '\n'); console.log('[fix-v1] credential-patterns 追加', toAdd.length, '条 (bak:', bak + ')'); toAdd.forEach(r => console.log(' +', r.reason)); return { changed: true }; } function main() { const r1 = fixValidator(); const r2 = fixSnapshot(); const r3 = fixCredPatterns(); // 语法校验 let syntaxOk = true; for (const f of [SNAPSHOT, VALIDATOR]) { try { execSync('node --check "' + f + '"', { stdio: 'pipe' }); } catch (e) { syntaxOk = false; console.error('[SYNTAX FAIL]', path.basename(f), String(e.stderr || e.message).split('\n')[0]); } } // 刷新 rules-compiled if (r3.changed) { try { execSync('node "' + path.join(__dirname, '..', 'compile-rules.js') + '"', { stdio: 'pipe' }); console.log('[fix-v1] rules-compiled.json 已刷新'); } catch (e) { console.error('[fix-v1] compile-rules 失败:', e.message); } } console.log('\n[fix-v1] done. validator=' + (r1.changed ? '✓' : '-') + ', snapshot=' + (r2.changed ? '✓' : '-') + ', cred-patterns=' + (r3.changed ? '✓' : '-')); process.exit(syntaxOk ? 0 : 1); } main();