← 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
M README.mdM package.jsonA spotcheck.js
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 →