SPB Git

Repository Public

privacy-scan.mts 2.3 KB 51 lines typescript

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
Files main
Menu

Theme