← back to Domain Sniper
audit-noise: surface candidate NOISE_PATTERNS by suffix tally + add cellt.net
44a0139a9f36d0959480325cda6520ce5b8c8665 · 2026-05-12 17:54:14 -0700 · steve
Post-bounce, brand-typo-hits.jsonl grew by 6 new pay.cellt.net hits —
'cellt' is Levenshtein-2 from 'callr' (same false-positive shape as
beutl.in). Added /\.cellt\.net$/i to NOISE_PATTERNS and bounced
watcher (new PIDs 41799/41800).
audit-noise.js is a read-only tool: groups every brand-typo-hits row by
2-label and 3-label suffix, marks each row [filtered] vs [candidate]
against the current NOISE_PATTERNS, and emits paste-ready regex lines
for any 3+ hit suffix not yet filtered. Wired as 'npm run audit:noise'.
Current top-5 candidates are all bare-punycode IDNs (legit Wikimedia-style
homoglyph monitoring — not noise to suppress).
Files touched
A audit-noise.jsM brand-typo-watcher.jsM package.json
Diff
commit 44a0139a9f36d0959480325cda6520ce5b8c8665
Author: steve <steve@designerwallcoverings.com>
Date: Tue May 12 17:54:14 2026 -0700
audit-noise: surface candidate NOISE_PATTERNS by suffix tally + add cellt.net
Post-bounce, brand-typo-hits.jsonl grew by 6 new pay.cellt.net hits —
'cellt' is Levenshtein-2 from 'callr' (same false-positive shape as
beutl.in). Added /\.cellt\.net$/i to NOISE_PATTERNS and bounced
watcher (new PIDs 41799/41800).
audit-noise.js is a read-only tool: groups every brand-typo-hits row by
2-label and 3-label suffix, marks each row [filtered] vs [candidate]
against the current NOISE_PATTERNS, and emits paste-ready regex lines
for any 3+ hit suffix not yet filtered. Wired as 'npm run audit:noise'.
Current top-5 candidates are all bare-punycode IDNs (legit Wikimedia-style
homoglyph monitoring — not noise to suppress).
---
audit-noise.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++
brand-typo-watcher.js | 1 +
package.json | 1 +
3 files changed, 77 insertions(+)
diff --git a/audit-noise.js b/audit-noise.js
new file mode 100755
index 0000000..631583e
--- /dev/null
+++ b/audit-noise.js
@@ -0,0 +1,75 @@
+#!/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);
+}
+
+const tally2 = new Map();
+const tally3 = new Map();
+let total = 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 parts = d.split('.');
+ if (parts.length >= 2) {
+ const s = parts.slice(-2).join('.');
+ tally2.set(s, (tally2.get(s) || 0) + 1);
+ }
+ if (parts.length >= 3) {
+ const s = parts.slice(-3).join('.');
+ tally3.set(s, (tally3.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\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[candidate]\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[candidate]\x1b[0m';
+ console.log(` ${String(n).padStart(5)} ${s.padEnd(40)} ${flag}`);
+}
+
+const candidates = ranked2.filter(([s]) => !isFiltered(s) && tally2.get(s) >= 3);
+if (candidates.length) {
+ console.log(`\n candidate NOISE_PATTERNS to consider adding (≥3 hits, not yet filtered):`);
+ for (const [s, n] of candidates.slice(0, 10)) {
+ console.log(` /\\.${s.replace(/\./g, '\\.')}$/i, // ${n} hits`);
+ }
+ console.log('');
+}
diff --git a/brand-typo-watcher.js b/brand-typo-watcher.js
index 067d6d9..5cde839 100644
--- a/brand-typo-watcher.js
+++ b/brand-typo-watcher.js
@@ -44,6 +44,7 @@ const NOTIFY = process.env.NOTIFY !== '0';
const NOISE_PATTERNS = [
/\.haplorrhini\.com$/i, // Let's Encrypt internal HTTP-01 challenge subdomains
/\.beutl\.in$/i, // Beutl video-editor app — Levenshtein-2 collision with "butlr"
+ /\.cellt\.net$/i, // pay.cellt.net (Korean payment service) — Lev-2 collision with "callr"
];
function isKnownNoise(d) { return NOISE_PATTERNS.some((r) => r.test(d)); }
diff --git a/package.json b/package.json
index fcfeb54..aedd5eb 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"honeypot:v2:generate": "node honeypot-v2.js generate",
"honeypot:v2:check": "node honeypot-v2.js check",
"inspect": "node inspect.js",
+ "audit:noise": "node audit-noise.js",
"register": "node register.js"
},
"dependencies": {
← 855ea94 readme: v1 vs v2 head-to-head + 4.20h slice 0-1 re-check #2
·
back to Domain Sniper
·
brand-typo-watcher: idle counter + stale-log marker in per-l fa1502d →