bookworm-smart-assistant/scripts/patches/patch-ssrf-ipv6-rfc1918.js
Bookworm Admin b7a8e29d21 release: v6.7.0 - OTA E2E test release
- 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)
2026-04-27 17:59:44 +08:00

98 lines
3.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* patch-ssrf-ipv6-rfc1918.js
*
* P0 升级:补全 constitution-precheck.js 的 hidden-network-egress 规则,
* 覆盖 IPv6 link-local + Teredo + 6to4 + NAT64 + RFC1918 全段。
*
* 协议: .bak + sentinel + 原子写
*/
'use strict';
const fs = require('fs');
const path = require('path');
const TARGET = path.join(__dirname, '..', '..', 'hooks', 'constitution-precheck.js');
const SENTINEL = 'PATCH-SSRF-IPv6-RFC1918-V1';
// 黑名单段(这些命中时**不**触发隐藏出站告警,视为合法本地访问)
// 来源对齐 OpenClaw src/shared/net/ip.ts
const BLOCKED_TARGETS = [
'localhost',
'127\\.', // IPv4 loopback /8
'0\\.0\\.0\\.0',
'10\\.', // RFC1918 /8
'192\\.168\\.', // RFC1918 /16
'172\\.(?:1[6-9]|2\\d|3[01])\\.', // RFC1918 /12
'169\\.254\\.', // Link-local IPv4
'::1', // IPv6 loopback
'fe80:', // IPv6 link-local
'fc[0-9a-f]{2}:', // IPv6 ULA
'fd[0-9a-f]{2}:', // IPv6 ULA
'2001:0?:', // Teredo
'2002:', // 6to4
'64:ff9b:', // NAT64
];
// 完整正则源(无需 backtick
const NEW_PATTERN_SRC =
'(?:https?\\.request|https?\\.get|fetch)\\s*\\(\\s*[\'"`]https?:\\/\\/(?!' +
BLOCKED_TARGETS.join('|') +
')';
// 输出形式pattern: /…/,
const NEW_LINE = " pattern: /" + NEW_PATTERN_SRC + "/, // " + SENTINEL + ": IPv6+RFC1918";
function main() {
if (!fs.existsSync(TARGET)) {
process.stderr.write('[ERROR] target not found: ' + TARGET + '\n');
process.exit(1);
}
let src = fs.readFileSync(TARGET, 'utf8');
if (src.includes(SENTINEL)) {
process.stdout.write('[SKIP] already patched\n');
process.exit(0);
}
const ts = new Date().toISOString().replace(/[:.]/g, '-');
const bakPath = TARGET + '.bak.' + ts;
fs.copyFileSync(TARGET, bakPath);
process.stdout.write('[BACKUP] ' + bakPath + '\n');
// 定位 hidden-network-egress 块内的 pattern 行
const blockStart = src.indexOf("id: 'hidden-network-egress'");
if (blockStart === -1) {
process.stderr.write('[ERROR] anchor `hidden-network-egress` not found\n');
process.exit(1);
}
const patternIdx = src.indexOf('pattern:', blockStart);
const patternEnd = src.indexOf('\n', patternIdx);
if (patternIdx === -1 || patternEnd === -1) {
process.stderr.write('[ERROR] pattern line not found\n');
process.exit(1);
}
// 找到该行的起始(前面缩进)
const lineStart = src.lastIndexOf('\n', patternIdx) + 1;
const before = src.slice(0, lineStart);
const after = src.slice(patternEnd);
const updated = before + NEW_LINE + after;
// 语法验证: 试构造 RegExp
try {
new RegExp(NEW_PATTERN_SRC);
} catch (e) {
process.stderr.write('[ERROR] new regex invalid: ' + e.message + '\n');
process.exit(1);
}
const tmpPath = TARGET + '.tmp.' + process.pid;
fs.writeFileSync(tmpPath, updated);
fs.renameSync(tmpPath, TARGET);
process.stdout.write('[OK] patched ' + TARGET + '\n');
process.stdout.write(' blocked: ' + BLOCKED_TARGETS.length + ' ranges\n');
}
if (require.main === module) main();