bookworm-smart-assistant/scripts/patches/patch-l5-must-invoke-every.js

101 lines
3.8 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* L5 修复 MUST_INVOKE 豁免漏洞 someevery + 移除 meta (2026-04-25)
*
* 问题: bwr-builder.js L48 intents.some(i => EXEMPT.has(i)) 判定豁免
* 任何复合意图只要含一个豁免词就跳过强制 Skill
* 结合 'meta' EXEMPT 列表中, "meta+audit"/"meta+debug"
* 审计/路由分析场景被全部绕过 MUST_INVOKE_SKILL
*
* 方案:
* 1. some every (全部 intent 都是豁免词才豁免)
* 2. EXEMPT 移除 'meta'
* 3. 保留 translate/explain/greeting/remember/continue/select/confirm
*
* 回归预测:
* - "你好" [greeting] 豁免 (every: true) OK
* - "翻译这段" [translate] 豁免 OK
* - "顺便看看配置" [meta, general] 强制 Skill (meta 已移除) OK
* - "记住深色主题" [remember] 豁免 OK
* - "解释下原理" [explain, debug] 强制 Skill (every: false) OK
*
* 幂等: sentinel `// L5-MUST-INVOKE-EVERY` 已存在则跳过
* 原子: tmp + rename备份: .bak.l5-must-invoke.<ms>
*/
'use strict';
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const TARGET = path.join(__dirname, '..', 'bwr-builder.js');
const SENTINEL = '// L5-MUST-INVOKE-EVERY';
const BAK_DIR = path.join(__dirname, 'bak');
const APPLY = process.argv.includes('--apply');
function syntaxCheck(file) {
execFileSync(process.execPath, ['--check', file], { stdio: 'pipe' });
}
function main() {
if (!fs.existsSync(TARGET)) {
console.error('[L5] bwr-builder.js not found.');
process.exit(2);
}
let src = fs.readFileSync(TARGET, 'utf8');
if (src.includes(SENTINEL)) {
console.log('[L5] sentinel found — already patched, skipping (idempotent).');
process.exit(0);
}
const OLD_LIST = "const MUST_INVOKE_EXEMPT_INTENTS = new Set(['translate', 'explain', 'greeting', 'meta', 'remember', 'continue', 'select', 'confirm']);";
const NEW_LIST = SENTINEL + " (2026-04-25 L5 修复 — meta 移出豁免, some→every)\n" +
"// 真豁免清单: 仅纯翻译/解释/问候/记忆/对话连续意图豁免 MUST_INVOKE_SKILL\n" +
"// 'meta' 移除原因: 审计/路由分析常被分类为 meta+x 复合意图, 历史用 some 判定导致豁免逃逸\n" +
"const MUST_INVOKE_EXEMPT_INTENTS = new Set(['translate', 'explain', 'greeting', 'remember', 'continue', 'select', 'confirm']);";
if (!src.includes(OLD_LIST)) {
console.error('[L5] OLD_LIST not found — abort.');
process.exit(2);
}
src = src.replace(OLD_LIST, NEW_LIST);
const OLD_SOME = '} else if (intents.some(i => MUST_INVOKE_EXEMPT_INTENTS.has(i))) {';
const NEW_EVERY = '} else if (intents.length > 0 && intents.every(i => MUST_INVOKE_EXEMPT_INTENTS.has(i))) {';
if (!src.includes(OLD_SOME)) {
console.error('[L5] OLD_SOME branch not found — abort.');
process.exit(2);
}
src = src.replace(OLD_SOME, NEW_EVERY);
if (!fs.existsSync(BAK_DIR)) fs.mkdirSync(BAK_DIR, { recursive: true });
const ts = Date.now();
const bakPath = path.join(BAK_DIR, 'bwr-builder.js.bak.l5-must-invoke.' + ts);
if (!APPLY) {
console.log('[L5] DRY RUN — pass --apply to write changes.');
console.log(' would backup to: ' + bakPath);
return;
}
const orig = fs.readFileSync(TARGET, 'utf8');
fs.writeFileSync(bakPath, orig);
const tmpPath = TARGET + '.tmp.' + ts;
fs.writeFileSync(tmpPath, src);
fs.renameSync(tmpPath, TARGET);
console.log('[L5] applied. backup at ' + bakPath);
try {
syntaxCheck(TARGET);
console.log('[L5] node --check syntax: PASS');
} catch (e) {
console.error('[L5] SYNTAX FAILED — rolling back!');
fs.copyFileSync(bakPath, TARGET);
process.exit(2);
}
}
main();