bookworm-smart-assistant/scripts/patches/patch-pipeline-hooks-fix-v1.js

152 lines
5.8 KiB
JavaScript
Raw Normal View History

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