[object Object]

← back to Domain Sniper

step 8: registrar-fingerprints.json + cross-ref in honeypot check

7b38b4a1599c1f625687fbd3ce49bffa702e15e0 · 2026-05-12 14:36:40 -0700 · Steve Abrams

Public-registrar fingerprint database covering the big-three drop-catchers
(NameBright/DropCatch, Pool.com, NameJet+SnapNames) plus OVH, Sav, Above,
GoDaddy Auctions, Dynadot, NameSilo, Namecheap, Cloudflare, Tucows, Gandi.
Each entry: IANA ID, NS regex patterns, country, aggressive-flag, notes.

honeypot.js check phase now annotates every sniped name with its operator
and prints a loud KNOWN-AGGRESSIVE warning when the actor is in the
aggressive list. Lookup proven against the butlr.app evidence (ns112.ovh.net
correctly resolves to OVH SAS · AGGRESSIVE).

Pivot from scheduled Step 6 (watch-drops.js): the free public drop-list
sources all sit behind anti-bot walls. Step 8 was higher-leverage for this
tick because it has zero external dependencies and instantly hardens the
honeypot check we're already waiting on.

Files touched

Diff

commit 7b38b4a1599c1f625687fbd3ce49bffa702e15e0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 12 14:36:40 2026 -0700

    step 8: registrar-fingerprints.json + cross-ref in honeypot check
    
    Public-registrar fingerprint database covering the big-three drop-catchers
    (NameBright/DropCatch, Pool.com, NameJet+SnapNames) plus OVH, Sav, Above,
    GoDaddy Auctions, Dynadot, NameSilo, Namecheap, Cloudflare, Tucows, Gandi.
    Each entry: IANA ID, NS regex patterns, country, aggressive-flag, notes.
    
    honeypot.js check phase now annotates every sniped name with its operator
    and prints a loud KNOWN-AGGRESSIVE warning when the actor is in the
    aggressive list. Lookup proven against the butlr.app evidence (ns112.ovh.net
    correctly resolves to OVH SAS · AGGRESSIVE).
    
    Pivot from scheduled Step 6 (watch-drops.js): the free public drop-list
    sources all sit behind anti-bot walls. Step 8 was higher-leverage for this
    tick because it has zero external dependencies and instantly hardens the
    honeypot check we're already waiting on.
---
 honeypot.js                 |  53 ++++++++++++++++++++-
 registrar-fingerprints.json | 112 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 164 insertions(+), 1 deletion(-)

diff --git a/honeypot.js b/honeypot.js
index ad2ffcc..be2b9a7 100644
--- a/honeypot.js
+++ b/honeypot.js
@@ -19,9 +19,37 @@ const fs = require('fs');
 const path = require('path');
 
 const DATA_DIR = path.join(__dirname, 'data');
+const FINGERPRINTS_FILE = path.join(__dirname, 'registrar-fingerprints.json');
 const args = process.argv.slice(2);
 const subcommand = args[0] || 'help';
 
+// Load known-aggregator fingerprints (file is always present in repo)
+let FINGERPRINTS = { operators: {} };
+try {
+  FINGERPRINTS = JSON.parse(fs.readFileSync(FINGERPRINTS_FILE, 'utf8'));
+} catch (e) {
+  // Non-fatal: just means we can't annotate operators
+}
+
+function lookupOperator(fp) {
+  if (!fp) return null;
+  // First try by IANA ID (most reliable)
+  if (fp.registrarIanaId && FINGERPRINTS.operators[fp.registrarIanaId]) {
+    return { id: fp.registrarIanaId, ...FINGERPRINTS.operators[fp.registrarIanaId] };
+  }
+  // Fall back to NS pattern match
+  for (const ns of fp.nameservers || []) {
+    for (const [id, op] of Object.entries(FINGERPRINTS.operators)) {
+      for (const pat of op.nsPatterns || []) {
+        try {
+          if (new RegExp(pat).test(ns)) return { id, ...op };
+        } catch {}
+      }
+    }
+  }
+  return null;
+}
+
 // ---------- deterministic RNG so we can reproduce the batch if needed ----------
 function mulberry32(a) {
   return function () {
@@ -298,9 +326,12 @@ async function check(batchPath) {
   const snipedItems = results.items.filter((i) => i.sniped);
   for (const item of snipedItems) {
     item.attackerFingerprint = whoisFingerprint(item.domain);
+    item.knownOperator = lookupOperator(item.attackerFingerprint);
     const f = item.attackerFingerprint;
+    const op = item.knownOperator;
+    const opTag = op ? (op.aggressive ? `\x1b[31m[KNOWN-AGGRESSIVE: ${op.name}]\x1b[0m` : `[${op.name}]`) : '[unknown operator]';
     console.log(
-      `  ${item.domain.padEnd(14)}  registrar=${(f.registrar || '?').slice(0, 30).padEnd(30)} ns=${(f.nameservers[0] || '?').slice(0, 28)}`,
+      `  ${item.domain.padEnd(14)}  registrar=${(f.registrar || '?').slice(0, 26).padEnd(26)} ns=${(f.nameservers[0] || '?').slice(0, 26).padEnd(26)} ${opTag}`,
     );
     await sleep(800);
   }
@@ -340,6 +371,26 @@ async function check(batchPath) {
       console.log(`    abuse:        ${snipedItems[0].attackerFingerprint.registrarAbuseEmail || '(none in WHOIS)'}`);
       console.log(`    countries:    ${[...top[1].countries].join(', ') || '(privacy-protected)'}`);
     }
+
+    // Cross-reference against known-aggressive operator database
+    const knownAggressive = snipedItems.filter((i) => i.knownOperator && i.knownOperator.aggressive);
+    if (knownAggressive.length) {
+      console.log(`\n  \x1b[31m▲ KNOWN-AGGRESSIVE OPERATORS DETECTED:\x1b[0m`);
+      const byOp = {};
+      for (const item of knownAggressive) {
+        const k = item.knownOperator.name;
+        byOp[k] = byOp[k] || { count: 0, country: item.knownOperator.country, notes: item.knownOperator.notes, examples: [] };
+        byOp[k].count++;
+        byOp[k].examples.push(item.domain);
+      }
+      for (const [name, v] of Object.entries(byOp).sort((a, b) => b[1].count - a[1].count)) {
+        console.log(`    ${name} (${v.country})  —  ${v.count}/${snipedItems.length} sniped names`);
+        console.log(`      example: ${v.examples[0]}`);
+        console.log(`      notes:   ${v.notes}`);
+      }
+    } else {
+      console.log(`\n  no match against known-aggressive operator database — this attacker is new. Add their fingerprint to registrar-fingerprints.json.`);
+    }
   } else {
     console.log(`\n  no snipes detected. either (a) we got lucky, (b) the aggregator paused, (c) different leak path. consider re-running with different channels or larger N.`);
   }
diff --git a/registrar-fingerprints.json b/registrar-fingerprints.json
new file mode 100644
index 0000000..928358d
--- /dev/null
+++ b/registrar-fingerprints.json
@@ -0,0 +1,112 @@
+{
+  "_about": "Public-registrar fingerprint database used by honeypot.js check phase to cross-reference WHOIS evidence against known drop-catch / aggregator operators. 'aggressive' flags operators with documented snipe / drop-catch infrastructure. Not exhaustive; expand as new actors surface.",
+  "_updated": "2026-05-12",
+  "_schema": {
+    "ianaId": "registrar IANA ID (from WHOIS 'Registrar IANA ID' field)",
+    "name": "canonical registrar name",
+    "nsPatterns": "regex patterns matched against authoritative NS root domains",
+    "country": "registrar country (ISO 3166)",
+    "aggressive": "true if known to operate drop-catch / aggregator infrastructure",
+    "notes": "freeform"
+  },
+  "operators": {
+    "433": {
+      "name": "OVH SAS",
+      "nsPatterns": ["^ns\\d+\\.ovh\\.net$", "^dns\\d+\\.ovh\\.net$"],
+      "country": "FR",
+      "aggressive": true,
+      "notes": "Confirmed snipe on butlr.app 2026-05-12 with ns112.ovh.net/dns112.ovh.net. Common destination for European drop-catchers. Cheap registrations make sniping economic."
+    },
+    "1606": {
+      "name": "NameBright / DropCatch",
+      "nsPatterns": ["^ns\\d+\\.namebrightdns\\.com$", "^ns\\d+\\.aftermarket\\.com$"],
+      "country": "US",
+      "aggressive": true,
+      "notes": "DropCatch.com is one of the three major drop-catch services. Owns hundreds of credentialed connections to TLD registries to race-grab pending-delete domains."
+    },
+    "1330": {
+      "name": "Pool.com / TurnCommerce",
+      "nsPatterns": ["^ns\\d+\\.poolregistry\\.com$", "^ns\\d+\\.parkingcrew\\.net$"],
+      "country": "US",
+      "aggressive": true,
+      "notes": "Pool.com is the second of the big-three drop-catchers. Long-tail dictionary-based filtering, immediate auction listing."
+    },
+    "64": {
+      "name": "Web.com / NameJet / SnapNames",
+      "nsPatterns": ["^ns\\d+\\.snapnames\\.com$", "^ns\\d+\\.namejet\\.com$", "^ns\\d+\\.web\\.com$"],
+      "country": "US",
+      "aggressive": true,
+      "notes": "Third major drop-catcher. NameJet auctions, SnapNames retail. Aggressive on premium .com / .net."
+    },
+    "609": {
+      "name": "Sav.com",
+      "nsPatterns": ["^ns\\d+\\.sav\\.com$"],
+      "country": "US",
+      "aggressive": true,
+      "notes": "Newer entrant, aggressive on short-form .com and short-LL.io / .ai snipes. Public auctions follow."
+    },
+    "1170": {
+      "name": "Above.com / Trellian",
+      "nsPatterns": ["^ns\\d+\\.above\\.com$", "^ns\\d+\\.trellian\\.com$"],
+      "country": "AU",
+      "aggressive": true,
+      "notes": "Long-tail drop-catch + parking + PPC monetization. Common destination for sniped names that get parked rather than auctioned."
+    },
+    "146": {
+      "name": "GoDaddy / GoDaddy Auctions",
+      "nsPatterns": ["^ns\\d+\\.domaincontrol\\.com$"],
+      "country": "US",
+      "aggressive": true,
+      "notes": "GoDaddy operates its own auction marketplace and TDNAM (after-market). Snipes through GoDaddy Auctions land at domaincontrol.com NS."
+    },
+    "472": {
+      "name": "Dynadot",
+      "nsPatterns": ["^ns\\d+\\.dynadot\\.com$"],
+      "country": "US",
+      "aggressive": false,
+      "notes": "Mostly retail. Occasional drop-catch activity. Not a primary aggregator."
+    },
+    "1479": {
+      "name": "NameSilo",
+      "nsPatterns": ["^ns\\d+\\.namesilo\\.com$", "^ns\\d+\\.dnsowl\\.com$"],
+      "country": "US",
+      "aggressive": false,
+      "notes": "Retail registrar. Cheap renewals. Not a known aggregator."
+    },
+    "1861": {
+      "name": "Porkbun",
+      "nsPatterns": ["^curitiba\\.ns\\.porkbun\\.com$", "^salvador\\.ns\\.porkbun\\.com$", "^maceio\\.ns\\.porkbun\\.com$", "^fortaleza\\.ns\\.porkbun\\.com$", "^ns\\d*\\.porkbun\\.com$"],
+      "country": "US",
+      "aggressive": false,
+      "notes": "Retail-friendly registrar. Not an aggregator."
+    },
+    "1068": {
+      "name": "Namecheap",
+      "nsPatterns": ["^dns\\d+\\.registrar-servers\\.com$"],
+      "country": "US",
+      "aggressive": false,
+      "notes": "Retail registrar. Not a primary aggregator but resold to many."
+    },
+    "1910": {
+      "name": "Cloudflare Registrar",
+      "nsPatterns": ["^[a-z]+\\.ns\\.cloudflare\\.com$"],
+      "country": "US",
+      "aggressive": false,
+      "notes": "Wholesale-pricing retail registrar. No aftermarket activity."
+    },
+    "69": {
+      "name": "Tucows / OpenSRS / Hover",
+      "nsPatterns": ["^ns\\d+\\.tucows\\.com$", "^ns\\d+\\.hover\\.com$", "^ns\\d+\\.domainpeople\\.com$"],
+      "country": "CA",
+      "aggressive": false,
+      "notes": "Wholesale platform reselling through many small registrars. Identity-of-actual-actor is downstream."
+    },
+    "81": {
+      "name": "Gandi",
+      "nsPatterns": ["^ns\\d+\\.gandi\\.net$"],
+      "country": "FR",
+      "aggressive": false,
+      "notes": "Retail-focused European registrar. Not an aggregator."
+    }
+  }
+}

← 4aecb56 step 5: register.js — atomic registrar API with dry-run safe  ·  back to Domain Sniper  ·  step 9: README marks deny rules shipped df203a6 →