#!/usr/bin/env node
/**
* check-redline.mjs — 官网产物脱敏红线词扫描(CI 红线,对齐 design/CLAUDE.md §1 铁律 13)。
*
* 扫描对象:构建产物 dist/ 下所有 .html(含正文、属性、aria-label、
、meta)。
* 红线词:VPN(大小写敏感)、翻墙、科学上网、突破封锁、自由穿越、Go anywhere(不分大小写)。
* 例外(与仓库 ci/scan-redline.sh 一致):外部渠道 handle @PangolinVPN_bot / @pangolinvpn。
*
* 命中即非零退出(CI fail)。默认扫描 ./dist,可传参指定目录。
*/
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
import { join, resolve } from 'node:path';
const ROOT = resolve(process.argv[2] ?? 'dist');
if (!existsSync(ROOT)) {
console.error(`[redline] 找不到待扫描目录:${ROOT}(请先 npm run build)`);
process.exit(2);
}
/** 大小写敏感的红线词(与 bash 版一致:VPN 仅禁大写形态,避免误伤 pangolin.vpn 域名占位) */
const CASE_SENSITIVE = ['VPN', '翻墙', '科学上网', '突破封锁', '自由穿越'];
/** 大小写不敏感 */
const CASE_INSENSITIVE = [/go anywhere/gi];
/** 允许例外的渠道 handle(扫描前先抹去,避免误报) */
const WHITELIST = [/@[Pp]angolin[Vv][Pp][Nn]_?[Bb]ot/g, /@pangolinvpn/g];
function listHtml(dir) {
const out = [];
for (const name of readdirSync(dir)) {
const p = join(dir, name);
const st = statSync(p);
if (st.isDirectory()) out.push(...listHtml(p));
else if (name.endsWith('.html')) out.push(p);
}
return out;
}
let violations = 0;
const files = listHtml(ROOT);
for (const file of files) {
let text = readFileSync(file, 'utf8');
for (const w of WHITELIST) text = text.replace(w, '');
const hits = [];
for (const word of CASE_SENSITIVE) {
let idx = text.indexOf(word);
while (idx !== -1) {
hits.push({ word, ctx: text.slice(Math.max(0, idx - 24), idx + word.length + 24).replace(/\s+/g, ' ') });
idx = text.indexOf(word, idx + word.length);
}
}
for (const re of CASE_INSENSITIVE) {
for (const m of text.matchAll(re)) {
hits.push({ word: m[0], ctx: text.slice(Math.max(0, m.index - 24), m.index + m[0].length + 24).replace(/\s+/g, ' ') });
}
}
if (hits.length) {
violations += hits.length;
console.error(`❌ ${file}`);
for (const h of hits) console.error(` 红线词 [${h.word}] …${h.ctx}…`);
}
}
console.log(`→ 红线扫描完成:${files.length} 个 HTML 文件,${violations} 处命中。`);
if (violations) {
console.error('\n脱敏检查失败!禁用词:VPN、翻墙、科学上网、突破封锁、自由穿越、Go anywhere');
console.error('代用词:网络加速、极速畅连、稳定、加速线路、隐私保护、无日志');
process.exit(1);
}
console.log('✅ 脱敏扫描通过 — 未发现红线词。');