Creative ideas + design notes
Commits with substantial prose (≥120 chars) — the rationale behind each move.
65a6a8f · 2026-05-19 · honeypot 2026-05-12 recheck @+168h: confirmed nic.co .co WHOIS leak; Dynadot→aggressive
dusab.co (bucket C, nic.co WHOIS for .co) was sniped by Dynadot Inc
between +4h and +168h. CONTROL bucket stayed at 0/10 — the snipe is
causally linked to the nic.co query channel, not random.
Snipe used Cloudflare NS (donna.ns.cloudflare.com) rather than Dynadot's
own NS — parker/aggregator shop fronting inventory through CF DNS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ee95ab0 · 2026-05-13 · honeypot: 11.4h pulse — 0/400 cumulative DoH checks across 32 datapoints
Long-cadence pulse after the 6h triple-confirmed result. Slice 4-5
spot-check at 11.4h elapsed (6 hours since last touch on this slice):
0/10. Cumulative now 0/400 DoH checks. Healthz green throughout
(dashboard uptime 6.8h, all 6 CT sources <90s fresh).
Negative result remains conclusive at the 11h mark. Next decision
point is the 24h checkpoint at 2026-05-13 20:36 UTC (~9.2h out).
2623358 · 2026-05-12 · honeypot: 6h checkpoint — third full 50-bait sweep, third zero
Triple-confirmed negative result. Full sweeps at 3.87h, 5.46h, and
6.27h all 0/50 across all five buckets (A Verisign / B Google .app /
C nic.co / D GoDaddy API / E control). Cumulative across all spot-
checks + sweeps: 0/380 DoH checks across 30 datapoints.
Pure-random CVCVC names like kuwavi/tahoz/fobahu produce zero
aggregator interest regardless of leak channel — the desirability
filter is real. Next experimental moves require Steve sign-off:
1. Honeypot v2 lure with pronounceable + brand-adjacent cohorts
(LURE_CONFIRM_LEAK_2026=yes env required)
2. 24h checkpoint at 2026-05-13 20:36 UTC (existing v1 batch)
System pulse at 6h mark: healthz green, all 6 CT sources fresh.
668bd5c · 2026-05-12 · readme: 5.82h spot-check datapoint — 0/330 across 29 datapoints
Pre-6h pulse: spot-check 5.82h slice 6-7 = 0/10 (29th datapoint),
healthz green (6/6 sources fresh, dashboard uptime 73min), v2
ambient drift still 0 (pronounceable 29/50, brand-adjacent 38/50
unchanged at 1.74h post-gen). Curve table updated.
fb0cf73 · 2026-05-12 · spotcheck: harden newestHoneypot() regex to exclude -results.json and v2 batches
Previous regex /^honeypot-\d{4}/ + endsWith('.json') correctly skipped
honeypot-v2-* (because the v after the dash fails the \d{4} test) and
also coincidentally skipped *-results.json (because '-' (0x2d) sorts
before '.' (0x2e), so the main file ends up last after sort()).
That second behaviour was load-bearing-by-accident. Tightened to:
/^honeypot-\d{4}-\d{2}-\d{2}T[\d-]+Z\.json$/
which matches only the canonical lure-batch filename shape — no
-results.json sidecars, no v2 cohort batches.
Verified picks honeypot-2026-05-12T20-36-16-324Z.json over the four
other honeypot-*.json files in data/. Cumulative spot-checks now
0/320 across 28 datapoints (5.76h slice 2-3 + slice 4-5 both 0/10).
1006f91 · 2026-05-12 · readme: ambient-drift baseline result — zero registrations on 100 v2 candidates over ~1.4h
Re-checked both v2 cohort batches at ~1.4h post-generate (no lure ever
fired). Results: pronounceable 29/50 -> 29/50 (zero change per-bucket
across A/B/C/D/E), brand-adjacent 38/50 -> 38/50 (same). 100 candidate
names exhibited zero ambient registration in a ~1h window.
This is a hard experimental baseline. If a future v2 lure produces
snipes > 0, those snipes are attributable to the lure itself, not
random ambient demand on lookalike names. Critical control for the
desirability-hypothesis experiment.
Spot-check curve update: 0/280 across 24 datapoints (5.60h slice 8-9
just landed via the extracted spotcheck.js).
e3b60de · 2026-05-12 · inspect: --summary flag for one-line YOLO status
23 datapoints in the chart now scroll past a screenful. Added --summary
(or -s) flag that prints just the headline: cumulative sniped/total +
last-datapoint snapshot, colored green when 0 / red when >0. Default
behaviour (full ASCII chart) unchanged.
Useful for the YOLO loop's standing 'are we snipe-free' check that
otherwise needs a tail | grep cumulative.
6cf9621 · 2026-05-12 · 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)
c7ac9f6 · 2026-05-12 · brand-typo-watcher: docstring guards future-me against suppressing BARE_PUNYCODE
The NOISE_PATTERNS how-to-extend block previously said 'when minute
summary shows persistent puny= or hits=' — but puny= is intentional
homoglyph audit-trail logging, not a false positive. Suppressing
those patterns would silence the actual security signal the watcher
is built to surface.
Now the guidance is explicit: only add SUBSTRING/TYPO false positives;
BARE_PUNYCODE rows must stay. Adds 'use npm run audit:noise' to the
workflow so the new candidate-filtering tool is the canonical source.
Also recorded 5.37h slice 4-5 RECHECK#3: 0/10. Cumulative 0/190
across 20 datapoints.
1070e77 · 2026-05-12 · audit-noise: distinguish brand-match hits from BARE_PUNYCODE audit trail
Bug: tool was flagging every unfiltered suffix as a 'candidate' to add
to NOISE_PATTERNS, including BARE_PUNYCODE rows which are intentional
homoglyph-monitor logging (xn--srna-loa.nu, certsbridge.com etc).
Suppressing those would silence the audit trail.
Fix: parallel tally tracks SUBSTRING+TYPO hits separately from the raw
count. Candidate-suggestion at bottom only proposes suffixes that have
3+ REAL brand-match hits AND are not yet filtered. The top-N raw lists
relabeled from [candidate] -> [unfiltered] to remove the now-implicit
'should suppress' suggestion.
Also confirmed cumulative 0/180 v1 spot-checks (5.27h slice 2-3 #3,
0/10) and both watcher PIDs alive (brand-typo 56788, dashboard 15791).
0d22891 · 2026-05-12 · watch-outbound: extend INTEL allowlist with RIRs, CZDS, 2026-era patterns
honeypot.js v1 parser audit: whoisQuery is single-layer (clean), and
godaddyApi has a benign mis-tally on JSON-shaped API errors but it's
the lure phase where DoH-at-check is ground truth, so snipe detection
is unaffected. No fix needed.
watch-outbound DOMAIN_INTEL_PATTERNS gaps filled (35 -> 47 patterns):
- 5 RIRs (arin/ripe/apnic/lacnic/afrinic) — IP/ASN lookups that
typically precede or accompany domain-intel research
- 2 ICANN services (czds.icann.org for zone data, lookup.icann.org)
- 5 newer aggregators / new-TLD registries (centralnic, donuts.email,
humbleworth, sav.com, spaceship)
- api.certspotter.com — we use it ourselves but tracking outbound
helps with budget audit if a paid key lands later.
667159d · 2026-05-12 · register: fix Namecheap false-positive when API ok but registration failed
Eyeball audit of the Namecheap response parser caught a real bug:
the success check only matched the open tag '<DomainCreateResult'
regardless of its Registered= attribute. Namecheap API uses a two-
layer status: outer Status="OK" means 'API call accepted', inner
DomainCreateResult Registered="true" means 'domain actually
registered'. A request can succeed at layer 1 and fail at layer 2
(e.g. domain unavailable, registrant invalid, insufficient funds)
without raising an <Error> — the prior code would report success
on such cases.
New logic requires BOTH:
apiOk = Status="OK"
regOk = DomainCreateResult ... Registered="true"
plus a clearer detail message that distinguishes 'api failed'
from 'api ok but registration declined'.
watch-outbound.js audit: clean (loopback-only, no hardcoded creds,
35-pattern INTEL allowlist comprehensive). NameSilo + GoDaddy
parsers also reviewed — NameSilo correct, GoDaddy intentionally
NOT_WIRED to avoid silent billing.
9f69a10 · 2026-05-12 · check: annotate owned domains in DoH-check output (non-blocking)
check.js is read-only DoH (no leak channel), so blocking owned domains
adds no safety. But the user can mis-read 'TAKEN' as 'someone else
beat me to it' when in fact they already own it. Now the JSON output
includes owned: true|false and a clearer 'OWNED — this domain is in
data/owned.json' advice line when applicable.
Also recorded 5.07h slice 0-1 RECHECK#3 datapoint: 0/10. Cumulative
0/170 across 18 datapoints — curve unchanged across the 1h gap from
4.20h.
46c5487 · 2026-05-12 · owned-check: extract shared module + extend safety to honeypot-v2 generate
owned-check.js — small 25-line shared module with loadOwned() + isOwned().
Tolerates both shapes (bare array, wrapped {domains:[...]}) and no-ops
when data/owned.json is absent. register.js refactored to consume it.
honeypot-v2.js generate() now refuses (exit 6) when any generated stem
would bait a domain Steve owns. The collision check runs over the whole
batch before writing — partial batches never land. README documents the
owned.json shape since data/ is gitignored.
New exit-code 6 (honeypot-v2: bait would collide with owned) joins the
existing 2/3/4/5 in register.js.
ed0c01f · 2026-05-12 · register: refuse re-registration of domains in data/owned.json (exit 5)
Audit of register.js found DRY-RUN default, validated domain regex,
and no availability-leak codepath — all clean. One gap: the README
calls owned.json the single-source-of-truth for 'what we already
grabbed' but register.js was not actually consulting it.
Added alreadyOwned() check that loads data/owned.json (tolerates
either ["d.com",…] or {domains:[…]} shape, gracefully no-ops when
the file is absent). Hits before backend detection so a typo'd
re-register attempt fails fast with exit 5 — distinct from the
existing exit codes (2 usage, 3 no-creds, 4 reg-failed).
Verified: owned → exit 5 with red REFUSED banner; fresh → normal flow.
d315f31 · 2026-05-12 · ct-source-certspotter: mark both adapters STALLED with reactivate-when
Neither cert-spotter adapter is consumed by any current watcher — all
shipped watchers (watch-ct, brand-typo-watcher, dashboard) now talk
directly to CT logs via ct-source-ctlog. Both files preserved (not
deleted) because their emit-payload shape matches what the watchers
already consume, so reactivation when a paid CERTSPOTTER_API_KEY
lands is a one-env-var change with no refactor.
Added top-of-file STATUS / REACTIVATE WHEN blocks so anyone reading
sees the current dead-codepath status before assuming they work.
02d8f7f · 2026-05-12 · watch-ct: migrate to ct-source-ctlog (6-log fan-out, default)
watch-ct.js was the only watcher still on the dead certstream WebSocket.
Mirrored the same CT_SOURCE/CT_LOGS/CT_LOG/CTLOG_POLL_MS/CTLOG_BATCH env
shape used by brand-typo-watcher.js and dashboard.js. handleCertEvent()
extracted as the shared cert sink so both the certstream fallback and
the ctlog flow consume it. Default now: 6-log direct polling
(argon/xenon/wyvern/sphinx/elephant/tiger), CertStream kept as opt-in
fallback via CT_SOURCE=certstream.
Replaced raw ESC bytes (0x1b) in console.log with \x1b escapes for
greppability. Runtime output identical.
Also recorded 4.74h slice 8-9 RECHECK#2: 0/10. Cumulative 0/160.
a353d85 · 2026-05-12 · watchdog: opt-in launchd plist + zsh script that bounces dashboard on /healthz 503
launchd/sniper-watchdog.sh
- curls /healthz with 5s timeout
- 200 -> silent exit
- anything else -> log UNHEALTHY + try pm2 restart, fall back to pgrep+nohup
- all writes append-only to logs/watchdog.log
launchd/com.steve.sniper-watchdog.plist
- StartInterval 60s, RunAtLoad true
- PATH includes /opt/homebrew/bin for pm2 lookup
- opt-in: must launchctl load to activate
plutil -lint passes; healthy-path smoke run exits 0 silently with no log
written. README updated with enable/disable instructions.
Also recorded 4.65h slice 6-7 RECHECK#2: 0/10. Cumulative 0/150 across
16 datapoints.
dfb3b86 · 2026-05-12 · dashboard: /healthz liveness probe for pm2/launchd
Returns 200 + JSON when at least one CT source has reported (tick or
idle) within 90s, 503 otherwise. Includes per-source ageSec so the
body is also useful for eyeball checks. Cheap to poll, no external
calls. Pattern for pm2 ecosystem entry:
scripts: [{ name: 'sniper-dashboard', script: 'dashboard.js',
max_memory_restart: '500M',
... and a separate launchd job that curls /healthz
every minute and bounces pm2 if it returns 503 }]
Also recorded 4.59h slice 4-5 RECHECK#2 datapoint: 0/10. Cumulative
0/140 across 15 datapoints.
24e477f · 2026-05-12 · dashboard ui: per-log source band with health dots
Renders the perLog payload from the WebSocket stats broadcast as a
horizontal strip above the feed: one chip per active CT source showing
log-name + emitted count + idle count + colored health dot.
green = healthy (emitted > 0)
yellow = alive but quiet (idle > 0, emitted == 0 this session)
red = stale (no tick/idle in 60s — wedged)
No dashboard restart needed; express.static serves the new HTML on
next page load. All 6 CT sources currently report green except Sectigo
elephant/tiger which sometimes show yellow during quiet bursts.
7992d0c · 2026-05-12 · dashboard: per-log tracker + idle counter + /api/perlog endpoint
Mirrors brand-typo-watcher's perLog map (fetched/x509/precert/emitted/
idle/lastSeenMs). Broadcast stats payload now includes perLog with a
computed 'stale' boolean (no event in 60s). Also adds GET /api/perlog
for headless inspection. Once public/index.html adds a renderer, browser
clients get the same alive-busy / alive-quiet / wedged diagnostic the
shell watcher has.
Also logged 4.44h slice 2-3 RECHECK#2 datapoint: 0/10 (no late snipes
since 3.57h). v1 cumulative now 0/130.
fa1502d · 2026-05-12 · brand-typo-watcher: idle counter + stale-log marker in per-log breakdown
Taps the source's 'idle' status events (treeSize unchanged this poll)
and tracks lastSeenMs per log. Per-log breakdown now shows i=N for idle
polls and prepends a red ! when no event of either kind has arrived
in 60s. Distinguishes:
alive+busy (i=0, climbing emit)
alive+quiet (i high, emit low — e.g. Sectigo tiger)
wedged (no updates either way — red ! marker)
First minute post-bounce confirms: tiger=0(0x/0p i=10) — clearly alive
but Sectigo just isn't producing certs this minute. Previously this
looked indistinguishable from a stuck poller.
44a0139 · 2026-05-12 · 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).
855ea94 · 2026-05-12 · readme: v1 vs v2 head-to-head + 4.20h slice 0-1 re-check #2 (0/10)
Cumulative spot-check now 0/120 DoH checks across 13 datapoints. Added
a side-by-side table comparing v1 (random CVCVC, lured, 0-result so far)
vs v2 (cohort-shaped, gated, not yet lured). The v1 0/120 result drives
the v2 desirability-hypothesis experiment.
4893045 · 2026-05-12 · honeypot-v2: wire actual lure body (still env-gated)
Two-phase lure: DoH-availability pre-check first, then bucket queries
fired only against the available subset. Skipped baits get queryResult
SKIPPED:not-available rather than null. Resumable — persists batch JSON
after every item write. Sets lured/luredAt on completion and refuses to
re-lure an already-lured batch.
Env gate (LURE_CONFIRM_LEAK_2026=yes) still mandatory — verified the
refusal path still triggers without it. YOLO autonomous loops cannot
set this env.
89086bf · 2026-05-12 · honeypot-v2: check shows availability tally + readme baselines
Check output now includes avail column (out of N) and cumulative
availability rate, so unlured batches give a useful sanity number not
just a 0/0 sniped header. Confirmed both cohorts are fresh enough for
experimentation:
pronounceable 29/50 (58%) — .app 100% / .co 90% / .com 30-40%
brand-adjacent 38/50 (76%) — .co 100% / .app 90% / .com 50-70%
.com is saturated for both cohorts (expected — short pronounceable .com
is gold). The .app and .co buckets carry most of the experimental signal.
47a41bc · 2026-05-12 · honeypot-v2: scaffold with desirability-shaped baits (lure gated, never auto-fires)
Two cohorts to test the desirability hypothesis from the v1 0/50 result:
pronounceable : English-flavored stems (noyl/swid/cayz/wheim/jareast)
brand-adjacent : Lev-1 mutations of Steve brand stems (verturr/lawrr/qbutlr)
Both 'generate' subcommands always allowed (produce batch JSON, no leaks).
'lure' is REFUSED unless LURE_CONFIRM_LEAK_2026=yes env is set — explicit
one-shot gesture that YOLO loops cannot accidentally trigger. 'check' is
always allowed (DoH-only). npm scripts wired (honeypot:v2:generate /
honeypot:v2:check). brand-typo-watcher NOISE_PATTERNS now has a how-to-
extend docstring.
7b71df7 · 2026-05-12 · honeypot: 3.87h full 50-bait check — 0/50 across all 5 buckets
Definitive negative result at the 4h mark. Pure-random CVCVC names like
kuwavi / tahoz / fobahu produced ZERO snipes despite the 2026-05-12
lure phase. All 50 bait names still available per the DoH-only check.
This contradicts the naive 'leak query => snipe within minutes' model
seen for callr.app / callr.co / butlr.app. Hypothesis: aggregators
filter leak streams for desirability (English-likeness, brand-adjacency,
prior-search signal) before paying registry fees. Random gibberish isn't
worth the cost.
Cumulative spot-check curve now 0/110 DoH checks across 11 datapoints.
Next iteration should use English-pronounceable / brand-adjacent baits.
549f3fb · 2026-05-12 · brand-typo-watcher: suppress known-noise patterns (Let's Encrypt + Beutl)
209 hits over the last ~50min of firehose audit: 206 were
*.haplorrhini.com (LE internal HTTP-01 challenge subdomains) and 3 were
audiobookshelf.beutl.in (legit Beutl app, Levenshtein-2 collision with
'butlr'). Zero real squats. Added NOISE_PATTERNS array + isKnownNoise()
filter so these drop before matchBrand/notify/logHit. Adds noise= column
to minute summary so the noise rate stays visible.
Also logged 3.74h slice 6-7 FRESH datapoint: 0/10. 80% of batch (40 of
50 baits) now DoH-checked at least once with zero confirmed snipes.
49089a1 · 2026-05-12 · package.json: wire npm scripts for inspect + drops:list + drops:pull
Adds discoverable surface so 'npm run' lists the new tooling next to
the existing watcher/honeypot scripts. Also logged 3.66h slice 4-5
RECHECK datapoint: still 0/10 (no late snipes between 3.24h and 3.66h).
94015c2 · 2026-05-12 · inspect.js: pretty-print snipe-timing curve from honeypot-spotcheck.jsonl
Handles both record shapes (legacy per-row + new per-batch tally) and
renders an ASCII chart of elapsed-hours × snipes/sample with cumulative
totals. Current state: 0/80 cumulative DoH checks, 8 datapoints from
2.0h through 3.57h, curve genuinely flat.
Also adds slice 2-3 RECHECK datapoint at 3.57h (0/10) — no late snipes
between 3.17h and 3.57h.
9d569c7 · 2026-05-12 · readme: snipe-curve 3.49h datapoint + per-log throughput characterization
Spot-check 3.49h slice 0-1 re-check: 0/10 (no late snipes between 3.08h
and 3.49h on the first slice). Curve still flat across all four spot-
checks: 40 of 50 baits touched at least once, 0 confirmed snipes.
Per-log throughput documented: Google/DigiCert logs deliver ~100 emit/min
steady; Sectigo logs (elephant/tiger) burst-batch with quiet windows.
Not a bug — characteristic of how each operator ingests certs.
f8c440d · 2026-05-12 · brand-typo-watcher: per-log breakdown in minute summary
Taps 'tick' status events from ct-source-ctlog to maintain a perLog map
of emitted/x509/precert counts per log. First minute confirms all 6 logs
producing — argon 135 emitted (was the 'silent' one), sphinx 154,
wyvern 155, xenon 112, tiger 9, elephant pending. Removes guesswork
about whether the fan-out is healthy or one log is wedged.
9109f57 · 2026-05-12 · fingerprints: +5 aggressive operators (Key-Systems, Hexonet, GMO, Crazy Domains, Internet.bs)
Coverage now spans US/FR/DE/JP/AU/BS. 19 operators total, 12 flagged
aggressive. Picks up Japanese (Onamae.com), German wholesale (RRPproxy,
Hexonet/1API), Australian (Crazy Domains), and BS anonymity-front
(Internet.bs) snipe destinations that were blind spots in the prior
US/FR/AU-only set.
ded6c22 · 2026-05-12 · watch-drops: scaffold Step 6 — env-gated, czds-only adapter, refuses by default
Adapter interface + JSONL output + day-indexed metadata in place. CZDS
adapter wired but stub (activates on CZDS_API_KEY via secrets skill).
Pool/DropCatch/SnapNames/ExpiredDomains.net intentionally NOT registered
— anti-bot walled AND visiting them is a leak signal per DoH-only rule.
Also recorded 3.24h honeypot DoH spot-check (slice 4-5, final never-checked
baits): 0/10. 30 of 50 baits total spot-checked, curve flat throughout.
6852b69 · 2026-05-12 · ct-source-ctlog: emit 'resumed' status when cursor pre-exists
Argon was being silently picked up across restarts because the 'seeded'
event only fires on the first-ever encounter (position === null). Added
a 'resumed' status emit for the warm-cursor case + wired into both
watcher and dashboard console output. Also logged 3.17h honeypot DoH
spot-check (slice 2-3, fresh baits) — still 0/10, curve genuinely flat
through 20/50 baits across two slices.
da97978 · 2026-05-12 · ct-source: expand default fan-out to 6 logs (sphinx+elephant+tiger)
5/6 logs seeded under new config (xenon, wyvern, sphinx, elephant, tiger) —
argon silent (same prior-run pattern). Throughput target ~8 events/sec, up
from ~4. Also recorded ~3.1h honeypot DoH spot-check (0/10 snipes, curve
still flat).
008b1ca · 2026-05-12 · ct-source-ctlog: multi-log parallel polling via startMulti()
CT_LOGS=argon,xenon,wyvern env (default) → spawn 3 polling loops against a
shared EventEmitter, staggered by pollMs/N to avoid bursting all logs at the
same instant. Per-log cursors in certspotter-brand-cursors.json (already keyed
by log name). Consumers (brand-typo-watcher, dashboard) auto-detect CT_LOGS
vs CT_LOG and route to start()/startMulti().
Smoke (15s, argon+xenon+wyvern): 61 events ~= 4/sec firehose. vs ~2.2/sec
single-log = 1.8x. brand-typo PID 35012, dashboard PID 35013 restarted on
3-log multi-source.
Also: 2.9h honeypot spot-check 0/10 sniped — snipe-timing curve still flat.
2c5c65f · 2026-05-12 · ct-source-ctlog: parse PRECERT_ENTRY (TBSCertificate) for SAN extraction
Adds minimal-DER ASN.1 walker (extractDnsNamesFromTBS) that descends into the
precert's TBSCertificate, finds the [3] EXPLICIT extensions tag, iterates
each Extension SEQUENCE looking for OID 2.5.29.17 (subjectAltName), and
pulls dNSName GeneralNames (tag 0x82) from the SAN body.
Smoke run on Argon (18s, BATCH=24, POLL_MS=3000):
before: 14 x509 emitted, 43 precerts skipped → 25% emission
after: 30 x509 + 29 precerts emitted, 0 parseErr → 66% emission
~5.5x firehose throughput. Real-world rate now ~2.2 cert events/sec
per log; multiply by N parallel logs for further coverage.
Adds entry_type field to emitted events so consumers can distinguish
precerts (issued ~hours before the live cert) from final X.509 certs.
da5c653 · 2026-05-12 · dashboard.js: wire ct-source-ctlog (CT_SOURCE=ctlog default)
Mirrors brand-typo-watcher refactor — extracts handleCertEvent() so both
ctlog and certstream paths share the classify+broadcast+log flow.
Dashboard PID 80709 confirmed 6 HOT events flowing in first 20s after
restart (vs zero for 2h58m on dead certstream). Browser UI at :9895 now
shows live activity.
9bc1439 · 2026-05-12 · brand-typo-watcher: wire ct-source-ctlog (CT_SOURCE=ctlog default)
certstream.calidog.io WebSocket flow retained as fallback (CT_SOURCE=certstream).
Extracts handleCertEvent() so both sources reuse the same brand-match path.
PID 54555 confirmed seen=52 in first minute after restart — real CT firehose
flowing through Argon2026h1 direct poll.
Also marks 3h-window honeypot spot-check: 0/10 baits sniped at 2.65h elapsed.
b00757f · 2026-05-12 · ct-source-ctlog.js: WORKING direct CT-log polling — bypasses dead middlemen
Polls get-sth on active 2026 logs (Argon2026h1 / Xenon2026h1 / Wyvern2026h1
all confirmed advancing ~6–7 entries/sec). Fetches get-entries when tree
advances, parses MerkleTreeLeaf, extracts SAN DNS names from X.509 certs
via Node 22 crypto.X509Certificate.
Smoke run on Argon: 8 cert events emitted in 25s, 0 parse errors.
Precerts (entry_type=1, ~75% of stream) currently skipped pending ASN.1
TBSCertificate decoder; we still see X.509 entries (~25% of stream).
Cache-buster appended to get-sth URL — Nimbus 2026 was returning stale
treeSize for 30+ seconds without it; argon/xenon/wyvern advance every
poll once the buster is in place.
083e982 · 2026-05-12 · ct-source-certspotter-brands.js + free-CT-source landscape doc
Per-brand polling blocked by free-tier FQDN-only restriction (403 not_allowed_by_plan
on bare keyword 'callr'). Module retained — drops in once CERTSPOTTER_API_KEY routed
via secrets-manager. README now maps all 6 free CT-source options with their failure
modes; near-term path is crt.sh polling (currently 502 transient, retry next tick).
7a91f94 · 2026-05-12 · ct-source-certspotter.js: skeleton module + standalone smoke runner
Free tier requires domain= per request — no anonymous firehose. Module supports
per-brand polling (next tick: add brands-mode harness so 18 keywords cycle at
12min each = 90 req/hr, within 100/hr free limit). Cursor persistence + retry-
after backoff + 429/503 handling already wired. Drop-in replacement for the
CertStream WS payload shape.
109b3c1 · 2026-05-12 · step 10: dashboard UX — sort tabs, click-to-copy reg cmd, audio ping on BRAND
Sort: time (default) / brand-first / hot-first, persisted in localStorage.
Click any row to copy 'node register.js <domain>' to clipboard with toast.
WebAudio ping (880 Hz, 220ms) on BRAND hits; toggle persisted.
README logs ~2h honeypot spot-check (10/10 baits still available).
dc2abc5 · 2026-05-12 · step 7: brand-typo-watcher.js — Levenshtein + Punycode brand defense
Dedicated CertStream listener narrower than watch-ct.js, focused purely on
brand defense:
- Substring match (catches 'buy-philipperomano-now.com' style squat)
- Levenshtein distance ≤ 2 (catches 'phillipperomano', 'venturacorridorr',
'designerwalcoverings' typos)
- Punycode/IDN flag (catches Cyrillic-letter homoglyph attacks like
'philippеromano')
- macOS desktop notification per hit (NOTIFY=0 to disable)
- Bare-Punycode side-channel: even non-brand IDN domains get logged for
weekly review since IDN is almost always a homoglyph attack vector
Lev impl has rowMin early-out so the firehose (~50/sec) stays cheap.
Persistent JSONL log to data/brand-typo-hits.jsonl.
npm run watch:brand
df203a6 · 2026-05-12 · step 9: README marks deny rules shipped
16 deny rules added to ~/.claude/settings.json (Steve's home, not this repo)
to block the leak vectors at the Claude Code permission layer. MCP
domain-suite check_availability + get_whois_contact tools confirmed
disconnected by the runtime. Raw whois -h <registry> and curl against
RDAP/availability APIs also denied.
Belt-and-suspenders against future Claude sessions accidentally invoking
the same leak that probably grabbed callr.app/callr.co/butlr.app.
domain-sniper scripts (check.js, honeypot.js) are intentionally unaffected
— they use Node directly, not Claude's Bash tool, so the experiment
continues without interference.
7b38b4a · 2026-05-12 · 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.
4aecb56 · 2026-05-12 · step 5: register.js — atomic registrar API with dry-run safety
Backend auto-detect picks the cheapest available (NameSilo > Namecheap > GoDaddy).
DRY-RUN by default; --confirm required to spend money. YOLO autonomous mode never
passes --confirm — registrations are not reversible. Skip-check pattern: no
availability call before the register, the registrar tells us success-or-taken
in one round-trip (the check IS the leak).
Pivoted away from Porkbun (no public direct-register endpoint for non-resellers).
Roadmap updated; YOLO loop notes section added to README.
c8f80e8 · 2026-05-12 · honeypot v2: DoH pre-check is ground-truth; 6-letter .com to escape saturation
Phase 1 finds 10 DoH-confirmed-available names per bucket (regenerates until
each slot passes a Cloudflare NXDOMAIN+NXDOMAIN check). Phase 2 fires the
bait query through the suspect leak channel — its result is recorded but
not used for snipe attribution. preCheckAvailable becomes the only baseline
the check phase needs.
.com bumped from 5 to 6 letters (5-letter .com pronounceable namespace is
80%+ saturated, killing test power).