#!/usr/bin/env node /** * patch-sanitize-v6-fix-replace.js * * 修复 sanitize v6.0 在 kv/json/cli/token 处理中 * 错误把 String.replace 第 N 个非字符串参数 (offset:number, namedGroups:object) * 当作 token 导致 token.slice is not a function 的 bug。 * * 修复方法: 过滤 args 仅保留 typeof === 'string' 且非空。 * * 协议: 走 patches/ + sentinel + 原子写 */ 'use strict'; const fs = require('fs'); const path = require('path'); const TARGET = path.join(__dirname, '..', 'sanitize.js'); const SENTINEL_OLD = 'SANITIZE-V6-17PATTERNS'; const SENTINEL_NEW = 'SANITIZE-V6-FIX-REPLACE'; 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_OLD)) { process.stderr.write('[ERROR] base v6.0 patch not applied yet. Run patch-sanitize-v6-17patterns.js first.\n'); process.exit(1); } if (src.includes(SENTINEL_NEW)) { process.stdout.write('[SKIP] already patched (fix sentinel found)\n'); process.exit(0); } // 替换块: kv/json/cli + jwt/token/bearer/telegram const OLD_BLOCK = " } else if (type === 'kv' || type === 'json' || type === 'cli') {\n" + " // 抓最后一个非空捕获组作为 token\n" + " result = result.replace(re, function() {\n" + " const args = Array.from(arguments);\n" + " const m = args[0];\n" + " const groups = args.slice(1, -2).filter(Boolean);\n" + " const token = groups[groups.length - 1] || m;\n" + " return m.replace(token, maskToken(token));\n" + " });\n" + " } else if (type === 'jwt' || type === 'token' || type === 'bearer' || type === 'telegram') {\n" + " result = result.replace(re, function(m, g1) {\n" + " const token = g1 || m;\n" + " return m.replace(token, maskToken(token));\n" + " });\n" + " }"; const NEW_BLOCK = " } else if (type === 'kv' || type === 'json' || type === 'cli') {\n" + " // " + SENTINEL_NEW + ": 过滤非字符串参数 (offset:number / namedGroups:object)\n" + " result = result.replace(re, function() {\n" + " const args = Array.from(arguments);\n" + " const m = args[0];\n" + " const strs = args.slice(1).filter(function(a){ return typeof a === 'string' && a.length > 0; });\n" + " const token = strs[strs.length - 1];\n" + " if (!token || typeof token !== 'string') return m;\n" + " return m.split(token).join(maskToken(token));\n" + " });\n" + " } else if (type === 'jwt' || type === 'token' || type === 'bearer' || type === 'telegram') {\n" + " result = result.replace(re, function(m, g1) {\n" + " var token = (typeof g1 === 'string' && g1.length > 0) ? g1 : m;\n" + " if (typeof token !== 'string') return m;\n" + " return m.split(token).join(maskToken(token));\n" + " });\n" + " }"; if (!src.includes(OLD_BLOCK)) { process.stderr.write('[ERROR] expected block not found. v6.0 file structure changed?\n'); process.exit(1); } // 备份 const ts = new Date().toISOString().replace(/[:.]/g, '-'); const bakPath = TARGET + '.bak.' + ts; fs.copyFileSync(TARGET, bakPath); process.stdout.write('[BACKUP] ' + bakPath + '\n'); const updated = src.replace(OLD_BLOCK, NEW_BLOCK); // 语法验证: 试 require 临时文件 const tmpPath = TARGET + '.tmp.' + process.pid; fs.writeFileSync(tmpPath, updated); try { delete require.cache[require.resolve(tmpPath)]; require(tmpPath); // 加载验证 fs.renameSync(tmpPath, TARGET); process.stdout.write('[OK] sanitize.js fix applied (replace bug fixed)\n'); } catch (e) { fs.unlinkSync(tmpPath); process.stderr.write('[ERROR] syntax check failed: ' + e.message + '\n'); process.exit(1); } } if (require.main === module) main();