- VERSION file as authoritative version source - export.mjs reads VERSION with package.json fallback - bw-ota.ps1 DryRun mode for safe testing - auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
101 lines
3.8 KiB
JavaScript
101 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* L5 修复 — MUST_INVOKE 豁免漏洞 some→every + 移除 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();
|