152 lines
5.8 KiB
JavaScript
152 lines
5.8 KiB
JavaScript
|
|
#!/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();
|