bookworm-smart-assistant/scripts/patches/patch-ssrf-ipv6-rfc1918.js

98 lines
3.3 KiB
JavaScript
Raw Permalink Normal View History

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