#!/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('✅ 脱敏扫描通过 — 未发现红线词。');