Repository Public
1/**2 * Scans what is actually shipped (data/, public/, built HTML and client bundles) for private infrastructure details:3 * IPv4 addresses, MAC addresses, internal domain names, tunnel addressing, credentials, image metadata.4 */5import { readdir, readFile, stat } from "node:fs/promises";6import { join } from "node:path";7import sharp from "sharp";89const RULES: [string, RegExp][] = [10 ["private IPv4", /\b(?:10|192\.168|172\.(?:1[6-9]|2\d|3[01])|100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7]))\.\d{1,3}\.\d{1,3}(?:\.\d{1,3})?\b/],11 ["public gateway address", /\b51\.161\.\d{1,3}\.\d{1,3}\b/],12 ["MAC address", /\b[0-9a-f]{2}(?::[0-9a-f]{2}){5}\b/i],13 ["internal domain", /[a-z0-9-]+\.maclustr\.io/i],14 ["tunnel/vpn detail", /\b(wg[01]|tailscale|wireguard)\b/i],15 // Framework bundles legitimately contain words such as "password" (input types); look for actual secrets instead.16 ["credential", /\b(?:sudo\s+-S|BEGIN [A-Z ]*PRIVATE KEY|(?:password|passwd|secret|api[_-]?key|token)\s*[:=]\s*["'][A-Za-z0-9+/_\-]{6,}["'])/i],17 ["serial number label", /serial\s*(number|no)/i],18 ["exact location", /saint-augustin|desmaures/i],19];20const TEXT = /\.(json|html|js|css|txt|xml|rsc|body|meta)$/;21const findings: string[] = [];22let files = 0;2324async function walk(dir: string) {25 for (const name of await readdir(dir).catch(() => [])) {26 const p = join(dir, name);27 const s = await stat(p);28 if (s.isDirectory()) {29 if (name === "cache" || name === "node_modules" || name === "dev") continue;30 await walk(p);31 } else if (/\.(jpe?g|avif|webp|png)$/i.test(name)) {32 files++;33 const m = await sharp(p).metadata();34 if (m.exif || m.xmp || m.iptc) findings.push(`${p}: embedded image metadata`);35 } else if (TEXT.test(name) && s.size < 6_000_000) {36 files++;37 const text = await readFile(p, "utf8");38 for (const [label, re] of RULES) {39 const m = text.match(re);40 if (m) findings.push(`${p}: ${label} → "${m[0]}"`);41 }42 }43 }44}45for (const dir of ["data", "public", ".next/server/app", ".next/static"]) await walk(dir);46if (findings.length) {47 console.error(`privacy scan FAILED (${findings.length}):\n` + findings.slice(0, 40).join("\n"));48 process.exit(1);49}50console.log(`privacy scan ok: ${files} shipped files, no private infrastructure detail found`);51