[object Object]

← back to Domain Sniper

spotcheck: extract reusable DoH-only honeypot sampler + 5.46h full sweep 0/50

6cf9621b8a205be3ad21477c0cbbfbc0e9374d61 · 2026-05-12 19:04:53 -0700 · steve

Twenty-plus ticks of pasting the same 12-line node -e spotcheck inline
finally got extracted. spotcheck.js takes a slice argument (0-1 / 2-3
/ 4-5 / 6-7 / 8-9 / all), reads the newest data/honeypot-*.json batch
(or HONEYPOT_BATCH=foo.json), DoH-checks each pick, appends a summary
row to data/honeypot-spotcheck.jsonl. Bug fix vs the inline scripts:
correct batchTs parsing (filesystem-safe -mmm-ms format -> ISO).

Major checkpoint: running 'node spotcheck.js all' produced the second
full 50-bait sweep at 5.46h elapsed — STILL 0/50. The 4h flat result
isn't a transient lucky window; it's holding 1.5 hours later. README
table updated. Cumulative across the whole spotcheck.jsonl: well over
200 DoH checks, all 0 snipes.

npm run honeypot:spotcheck wires it. Future YOLO ticks call:
  node spotcheck.js 0-1   (or 2-3, 4-5, 6-7, 8-9, all)

Files touched

Diff

commit 6cf9621b8a205be3ad21477c0cbbfbc0e9374d61
Author: steve <steve@designerwallcoverings.com>
Date:   Tue May 12 19:04:53 2026 -0700

    spotcheck: extract reusable DoH-only honeypot sampler + 5.46h full sweep 0/50
    
    Twenty-plus ticks of pasting the same 12-line node -e spotcheck inline
    finally got extracted. spotcheck.js takes a slice argument (0-1 / 2-3
    / 4-5 / 6-7 / 8-9 / all), reads the newest data/honeypot-*.json batch
    (or HONEYPOT_BATCH=foo.json), DoH-checks each pick, appends a summary
    row to data/honeypot-spotcheck.jsonl. Bug fix vs the inline scripts:
    correct batchTs parsing (filesystem-safe -mmm-ms format -> ISO).
    
    Major checkpoint: running 'node spotcheck.js all' produced the second
    full 50-bait sweep at 5.46h elapsed — STILL 0/50. The 4h flat result
    isn't a transient lucky window; it's holding 1.5 hours later. README
    table updated. Cumulative across the whole spotcheck.jsonl: well over
    200 DoH checks, all 0 snipes.
    
    npm run honeypot:spotcheck wires it. Future YOLO ticks call:
      node spotcheck.js 0-1   (or 2-3, 4-5, 6-7, 8-9, all)
---
 README.md    |   1 +
 package.json |   1 +
 spotcheck.js | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 109 insertions(+)

diff --git a/README.md b/README.md
index 3db50df..d26f04e 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ Spot-checks via DoH at increasing intervals to find where the snipe window close
 | ~3.49h | A/B/C/D/E (2 ea, slice 0-1 re-check) | 0 / 10 — no late snipes between 3.08h and 3.49h |
 | ~3.87h | full 50-bait sweep | **0 / 50** (A 0/10, B 0/10, C 0/10, D 0/10, E 0/10) |
 | ~4.20h | A/B/C/D/E (2 ea, slice 0-1 re-check #2) | 0 / 10 — **120 cumulative DoH checks, 0 snipes** |
+| ~5.46h | full 50-bait sweep (second) | **0 / 50** — flat result reinforced 1.5h after 4h checkpoint |
 | ~6h    | _pending_        | — |
 | ~24h   | full batch       | — |
 | ~48h   | full batch (final) | — |
diff --git a/package.json b/package.json
index aedd5eb..ba74095 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
     "drops:pull": "node watch-drops.js pull",
     "honeypot:lure": "node honeypot.js lure",
     "honeypot:check": "node honeypot.js check",
+    "honeypot:spotcheck": "node spotcheck.js",
     "honeypot:v2:generate": "node honeypot-v2.js generate",
     "honeypot:v2:check": "node honeypot-v2.js check",
     "inspect": "node inspect.js",
diff --git a/spotcheck.js b/spotcheck.js
new file mode 100755
index 0000000..4d419ce
--- /dev/null
+++ b/spotcheck.js
@@ -0,0 +1,107 @@
+#!/usr/bin/env node
+// spotcheck.js — small DoH-only sweep over a slice of a honeypot batch.
+//
+// Reads the most recent data/honeypot-*.json batch (or one named via the
+// HONEYPOT_BATCH env var), picks 2 baits per bucket from the requested slice
+// (0-1, 2-3, 4-5, 6-7, 8-9, or 'all' for the whole batch), DoH-checks each
+// for availability, and appends one summary row to data/honeypot-spotcheck.jsonl.
+//
+// Usage:
+//   node spotcheck.js 0-1          # 10 baits (2 per bucket A/B/C/D/E)
+//   node spotcheck.js all          # all 50 baits
+//   HONEYPOT_BATCH=foo.json node spotcheck.js 2-3
+//
+// Why: we'd been pasting the equivalent 12-line node -e inline every tick.
+// Extracting it makes the YOLO loop tighter and lets `node inspect.js`
+// keep its clean ASCII chart.
+
+const fs = require('fs');
+const path = require('path');
+const https = require('https');
+
+const DATA_DIR = path.join(__dirname, 'data');
+const OUT_FILE = path.join(DATA_DIR, 'honeypot-spotcheck.jsonl');
+
+function newestHoneypot() {
+  if (process.env.HONEYPOT_BATCH) return path.resolve(process.env.HONEYPOT_BATCH);
+  const files = fs.readdirSync(DATA_DIR).filter((f) => /^honeypot-\d{4}/.test(f) && f.endsWith('.json'));
+  if (!files.length) throw new Error('no honeypot-*.json batch found in data/');
+  files.sort();
+  return path.join(DATA_DIR, files[files.length - 1]);
+}
+
+const sliceArg = (process.argv[2] || '0-1').trim();
+const sliceRanges = {
+  '0-1': [0, 2], '2-3': [2, 4], '4-5': [4, 6], '6-7': [6, 8], '8-9': [8, 10],
+  all: [0, 10],
+};
+const range = sliceRanges[sliceArg];
+if (!range) {
+  console.error(`unknown slice: ${sliceArg}. valid: ${Object.keys(sliceRanges).join(', ')}`);
+  process.exit(2);
+}
+
+function dohNs(domain) {
+  return new Promise((resolve) => {
+    https.get(
+      'https://cloudflare-dns.com/dns-query?name=' + encodeURIComponent(domain) + '&type=NS',
+      { headers: { accept: 'application/dns-json' }, timeout: 5000 },
+      (res) => {
+        let body = ''; res.on('data', (c) => body += c);
+        res.on('end', () => {
+          try {
+            const j = JSON.parse(body);
+            const taken = (j.Answer || []).length > 0 || (j.Authority || []).some((a) => a.type === 6);
+            resolve({ status: j.Status, taken, stillAvail: j.Status === 3 || (j.Status === 0 && !taken) });
+          } catch { resolve({ status: -1, taken: false, stillAvail: false }); }
+        });
+      },
+    ).on('error', () => resolve({ status: -2, taken: false, stillAvail: false }));
+  });
+}
+
+(async () => {
+  const batchPath = newestHoneypot();
+  const batch = JSON.parse(fs.readFileSync(batchPath, 'utf8'));
+  // batchTs format on disk is `YYYY-MM-DDTHH-MM-SS-mmmZ` (filesystem-safe).
+  // Convert to ISO: keep the first two dashes (date), turn the next three into : : .
+  const iso = String(batch.batchTs).replace(/^(\d{4})-(\d{2})-(\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d+)Z$/, '$1-$2-$3T$4:$5:$6.$7Z');
+  const elapsedH = (Date.now() - new Date(iso).getTime()) / 3.6e6;
+
+  const byBucket = {};
+  for (const it of batch.items) (byBucket[it.bucket] = byBucket[it.bucket] || []).push(it);
+  const sample = [];
+  for (const [k, v] of Object.entries(byBucket)) sample.push(...v.slice(range[0], range[1]).map((x) => ({ ...x, bucket: k })));
+
+  const tally = {};
+  const items = [];
+  for (const it of sample) {
+    const r = await dohNs(it.domain);
+    const sniped = it.preCheckAvailable && !r.stillAvail;
+    tally[it.bucket] = tally[it.bucket] || { sniped: 0, total: 0, names: [] };
+    tally[it.bucket].total++;
+    if (sniped) { tally[it.bucket].sniped++; tally[it.bucket].names.push(it.domain); }
+    items.push({ bucket: it.bucket, domain: it.domain, status: r.status, stillAvail: r.stillAvail, sniped });
+    await new Promise((r) => setTimeout(r, 200));
+  }
+
+  const rec = {
+    spotcheckAt: new Date().toISOString(),
+    batchPath: path.relative(__dirname, batchPath),
+    batchTs: batch.batchTs,
+    elapsedHours: +elapsedH.toFixed(2),
+    sampleSize: sample.length,
+    sampleSlice: sliceArg,
+    tally,
+    items,
+  };
+  fs.appendFileSync(OUT_FILE, JSON.stringify(rec) + '\n');
+
+  console.log(`\n  elapsed: ${rec.elapsedHours}h | slice ${sliceArg} | batch ${path.basename(batchPath)}`);
+  for (const [k, v] of Object.entries(tally)) {
+    console.log(`    ${k}  ${v.sniped}/${v.total}  ${v.names.join(', ') || '(none)'}`);
+  }
+  const totalSniped = Object.values(tally).reduce((a, b) => a + b.sniped, 0);
+  const totalChecked = Object.values(tally).reduce((a, b) => a + b.total, 0);
+  console.log(`\n  this slice: ${totalSniped}/${totalChecked} sniped\n`);
+})().catch((e) => { console.error(e); process.exit(1); });

← c7ac9f6 brand-typo-watcher: docstring guards future-me against suppr  ·  back to Domain Sniper  ·  inspect: --summary flag for one-line YOLO status e3b60de →