← back to Domain Sniper
audit-noise.js
94 lines
#!/usr/bin/env node
// audit-noise.js — surface top candidate noise patterns from brand-typo-hits.jsonl.
//
// Groups every hit by the last 2 + 3 labels of the domain (so subdomain churn
// rolls up to the registrable parent), tallies occurrences, and flags any
// suffix that is NOT already in brand-typo-watcher's NOISE_PATTERNS as a
// candidate to add. Read-only — never writes back.
//
// Usage: node audit-noise.js [N] (default top-30)
const fs = require('fs');
const path = require('path');
const FILE = path.join(__dirname, 'data', 'brand-typo-hits.jsonl');
const TOP_N = parseInt(process.argv[2] || '30', 10);
// Mirror the NOISE_PATTERNS from brand-typo-watcher (keep in sync if you change either).
const CURRENT_FILTER = [
/\.haplorrhini\.com$/i,
/\.beutl\.in$/i,
/\.cellt\.net$/i,
];
const isFiltered = (suffix) => CURRENT_FILTER.some((r) => r.test('x.' + suffix));
if (!fs.existsSync(FILE)) {
console.error(`no hits file at ${FILE}`);
process.exit(2);
}
// Two parallel tallies:
// tally2/tally3 — every hit (raw audit view, includes BARE_PUNYCODE)
// brand2/brand3 — ONLY real brand-match hits (SUBSTRING/TYPO). These are
// the ones worth proposing as NOISE_PATTERNS additions —
// suppressing BARE_PUNYCODE rows would silence the
// intentional homoglyph-monitoring audit trail.
const tally2 = new Map();
const tally3 = new Map();
const brand2 = new Map();
const brand3 = new Map();
let total = 0;
let totalBrand = 0;
const lines = fs.readFileSync(FILE, 'utf8').split('\n').filter(Boolean);
for (const ln of lines) {
let r; try { r = JSON.parse(ln); } catch { continue; }
const d = (r.domain || '').toLowerCase().replace(/^\*\./, '');
if (!d) continue;
total++;
const isBrandHit = r.matchType === 'SUBSTRING' || r.matchType === 'TYPO';
if (isBrandHit) totalBrand++;
const parts = d.split('.');
if (parts.length >= 2) {
const s = parts.slice(-2).join('.');
tally2.set(s, (tally2.get(s) || 0) + 1);
if (isBrandHit) brand2.set(s, (brand2.get(s) || 0) + 1);
}
if (parts.length >= 3) {
const s = parts.slice(-3).join('.');
tally3.set(s, (tally3.get(s) || 0) + 1);
if (isBrandHit) brand3.set(s, (brand3.get(s) || 0) + 1);
}
}
const ranked2 = [...tally2.entries()].sort((a, b) => b[1] - a[1]).slice(0, TOP_N);
const ranked3 = [...tally3.entries()].sort((a, b) => b[1] - a[1]).slice(0, TOP_N);
console.log(`\n audit-noise :: ${lines.length} hit rows / ${total} domain entries / ${totalBrand} real brand-match hits (rest = BARE_PUNYCODE audit trail)\n`);
console.log(` top ${TOP_N} 2-label suffixes (registrable parent):`);
console.log(` ` + '-'.repeat(72));
for (const [s, n] of ranked2) {
const flag = isFiltered(s) ? '\x1b[32m[filtered]\x1b[0m' : '\x1b[33m[unfiltered]\x1b[0m';
console.log(` ${String(n).padStart(5)} ${s.padEnd(40)} ${flag}`);
}
console.log(`\n top ${TOP_N} 3-label suffixes (rolled-up subdomains):`);
console.log(` ` + '-'.repeat(72));
for (const [s, n] of ranked3) {
const flag = isFiltered(s) ? '\x1b[32m[filtered]\x1b[0m' : '\x1b[33m[unfiltered]\x1b[0m';
console.log(` ${String(n).padStart(5)} ${s.padEnd(40)} ${flag}`);
}
// Real candidates = BRAND-match suffixes only. BARE_PUNYCODE rows are the
// intentional homoglyph-monitor audit trail and must not be suppressed.
const ranked2Brand = [...brand2.entries()].sort((a, b) => b[1] - a[1]);
const candidates = ranked2Brand.filter(([s]) => !isFiltered(s) && brand2.get(s) >= 3);
if (candidates.length) {
console.log(`\n candidate NOISE_PATTERNS to add (≥3 real brand-match hits, not yet filtered):`);
for (const [s, n] of candidates.slice(0, 10)) {
console.log(` /\\.${s.replace(/\./g, '\\.')}$/i, // ${n} brand-match hits`);
}
console.log('');
} else {
console.log(`\n no candidate NOISE_PATTERNS — all brand-match suffixes either filtered or <3 hits.\n`);
}