← back to Domain Sniper
step 5: register.js — atomic registrar API with dry-run safety
4aecb56c5d72f593ff011f39b792c4dd882f1f83 · 2026-05-12 14:03:42 -0700 · Steve Abrams
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.
Files touched
M README.mdM package.jsonA register.js
Diff
commit 4aecb56c5d72f593ff011f39b792c4dd882f1f83
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue May 12 14:03:42 2026 -0700
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.
---
README.md | 26 +++++++---
package.json | 5 +-
register.js | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 185 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index e0d1968..b6b616e 100644
--- a/README.md
+++ b/README.md
@@ -27,12 +27,26 @@ We can't get the WHOIS query stream (it's gated to aggregator partners), but we
## Architecture (one step at a time)
-- [x] **Step 0** — leak-minimal availability checker (`check.js`, Cloudflare DoH, no WHOIS/RDAP) — built first so we can SAFELY verify a candidate before racing to register. Used as a utility inside the sniper.
-- [ ] **Step 1** — `watch-ct.js` — live CertStream WebSocket listener. Pipes every new cert through a filter and surfaces hits. Tells us what's getting registered RIGHT NOW.
-- [ ] **Step 2** — `watch-drops.js` — daily pull of pending-delete CSVs from public auction houses. Surfaces candidates dropping in the next 5 days.
-- [ ] **Step 3** — `brand-typo-watcher.js` — watches for typos of Steve's brands (designerwallcoverings, philipperomano, ventura-corridor, etc.) hitting the market, alerts immediately.
-- [ ] **Step 4** — `register.js` — atomic register via Porkbun direct API (skip availability check entirely; Porkbun returns success-or-taken). Bound to a hotkey for hand-typed candidates + auto-fired by filters above.
-- [ ] **Step 5** — `viewer/` — web dashboard listing live hits, with one-click register.
+- [x] **Step 0** — `check.js` — leak-minimal availability checker via Cloudflare DoH. No WHOIS/RDAP/registrar APIs.
+- [x] **Step 1** — `watch-ct.js` — live CertStream WebSocket listener with hot-pattern + brand-typo filters. JSONL logs.
+- [x] **Step 2** — `watch-outbound.js` — local HTTPS forward proxy that surfaces every domain-intel request leaving the machine. Used to ID which tool is leaking.
+- [x] **Step 3** — `dashboard.js` — live web viewer at :9895 of the CT firehose. Bloomberg-terminal UX with brand/hot filters.
+- [x] **Step 4** — `honeypot.js` — controlled leak-attribution hunt. Fires 50 DoH-confirmed-available baits across 5 channels (Verisign WHOIS, Google WHOIS .app, nic.co WHOIS, GoDaddy API, control). 24-48h later, WHOIS-fingerprints every snipe and clusters by registrar+IANA+NS to identify the attacker.
+- [x] **Step 5** — `register.js` — atomic registrar API call. Backends: NameSilo / Namecheap / GoDaddy. DRY-RUN default; pass `--confirm` to actually buy.
+- [ ] **Step 6** — `watch-drops.js` — daily pull of pending-delete CSVs from public auction houses. Surfaces names dropping in the next 5 days. Free sources: pool.com, dropcatch.com, snapnames.com, ICANN CZDS zone-diff.
+- [ ] **Step 7** — `brand-typo-watcher.js` — promote the brand-keyword logic out of `watch-ct.js` into its own job. Alerts immediately on hit.
+- [ ] **Step 8** — `registrar-fingerprints.json` — known aggregator signatures (OVH ns112.ovh.net, etc.) so the honeypot check phase can name-and-shame quickly.
+- [ ] **Step 9** — kill `mcp__domain-suite__check_availability` from `~/.claude.json` so it can never be invoked accidentally.
+- [ ] **Step 10** — extend dashboard with sort, click-to-register, sound alerts on brand hits.
+
+## YOLO loop notes
+
+When running in autonomous /loop mode, each tick must:
+- Be reversible (no `register.js --confirm` ever; no DNS changes; no email sends)
+- Produce one commit max
+- Use only local resources (no paid external LLM/API calls)
+- Check `data/honeypot-2026-05-12T20-36-16-324Z.json` status if 4h+ has elapsed since lure (early-tick snipes get logged but full attribution waits for 48h mark)
+- Pick the next unchecked Step from the list above
## Filters (what makes a candidate "hot")
diff --git a/package.json b/package.json
index 30a1712..bba0304 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,10 @@
"watch:ct": "node watch-ct.js",
"watch:outbound": "node watch-outbound.js",
"dashboard": "node dashboard.js",
- "watch:drops": "node watch-drops.js"
+ "watch:drops": "node watch-drops.js",
+ "honeypot:lure": "node honeypot.js lure",
+ "honeypot:check": "node honeypot.js check",
+ "register": "node register.js"
},
"dependencies": {
"express": "^5.2.1",
diff --git a/register.js b/register.js
new file mode 100644
index 0000000..181542a
--- /dev/null
+++ b/register.js
@@ -0,0 +1,161 @@
+#!/usr/bin/env node
+// register.js — atomic domain registration via direct registrar API.
+//
+// SAFETY: defaults to DRY-RUN. Pass --confirm to actually spend money.
+// In YOLO autonomous mode, NEVER pass --confirm — registrations are not reversible.
+//
+// Backend auto-detection (cheapest/simplest first):
+// - NAMESILO_API_KEY → NameSilo (~$9 .com, no IP whitelist)
+// - NAMECHEAP_API_USER + NAMECHEAP_API_KEY → Namecheap (needs IP whitelist + $50 prefund)
+// - GODADDY_KEY + GODADDY_SECRET → GoDaddy (needs payment on file)
+//
+// Skip-check pattern: this script DOES NOT call any availability API before
+// registering. The registrar tells us success-or-taken in one round-trip. The
+// "check" step IS the leak we're trying to avoid.
+//
+// Usage:
+// node register.js <domain> # dry-run
+// node register.js <domain> --confirm # actually buy
+
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+
+const DATA_DIR = path.join(__dirname, 'data');
+const LOG_FILE = path.join(DATA_DIR, 'registrations.jsonl');
+
+function detectBackend() {
+ if (process.env.NAMESILO_API_KEY) return 'namesilo';
+ if (process.env.NAMECHEAP_API_USER && process.env.NAMECHEAP_API_KEY) return 'namecheap';
+ if (process.env.GODADDY_KEY && process.env.GODADDY_SECRET) return 'godaddy';
+ return null;
+}
+
+function httpsRequest(opts, body) {
+ return new Promise((resolve, reject) => {
+ const req = https.request(opts, (res) => {
+ let d = '';
+ res.on('data', (c) => (d += c));
+ res.on('end', () => resolve({ status: res.statusCode, body: d }));
+ });
+ req.on('error', reject);
+ if (body) req.write(body);
+ req.end();
+ });
+}
+
+async function registerNamesilo(domain) {
+ const key = process.env.NAMESILO_API_KEY;
+ const url = `/api/registerDomain?version=1&type=xml&key=${encodeURIComponent(key)}&domain=${encodeURIComponent(domain)}&years=1&private=1&auto_renew=0`;
+ const r = await httpsRequest({ hostname: 'www.namesilo.com', path: url, method: 'GET' });
+ const code = (r.body.match(/<code>(\d+)<\/code>/) || [])[1];
+ const detail = (r.body.match(/<detail>([^<]+)<\/detail>/) || [])[1];
+ // NameSilo 300 = success
+ return { success: code === '300', code, detail, http: r.status };
+}
+
+async function registerNamecheap(domain) {
+ const user = process.env.NAMECHEAP_API_USER;
+ const key = process.env.NAMECHEAP_API_KEY;
+ const clientIp = process.env.NAMECHEAP_CLIENT_IP || '127.0.0.1';
+ const username = process.env.NAMECHEAP_USERNAME || user;
+ const contact = process.env.NAMECHEAP_CONTACT_JSON
+ ? JSON.parse(process.env.NAMECHEAP_CONTACT_JSON)
+ : null;
+ if (!contact) {
+ return {
+ success: false,
+ code: 'NO_CONTACT',
+ detail: 'NAMECHEAP_CONTACT_JSON env required (firstName/lastName/address1/city/stateProvince/postalCode/country/phone/emailAddress). Namecheap registration needs full registrant contact.',
+ };
+ }
+ const params = new URLSearchParams({
+ ApiUser: user, ApiKey: key, UserName: username, ClientIp: clientIp,
+ Command: 'namecheap.domains.create', DomainName: domain, Years: '1',
+ });
+ for (const role of ['Registrant', 'Tech', 'Admin', 'AuxBilling']) {
+ for (const [k, v] of Object.entries(contact)) {
+ params.set(role + k.charAt(0).toUpperCase() + k.slice(1), v);
+ }
+ }
+ const r = await httpsRequest(
+ { hostname: 'api.namecheap.com', path: '/xml.response?' + params.toString(), method: 'GET' },
+ null,
+ );
+ const success = /Status="OK"/.test(r.body) && /<DomainCreateResult/.test(r.body);
+ const errMatch = r.body.match(/<Error[^>]*>([^<]+)<\/Error>/);
+ return { success, code: success ? 'OK' : 'ERROR', detail: errMatch ? errMatch[1] : (success ? 'registered' : 'unknown'), http: r.status };
+}
+
+async function registerGodaddy(domain) {
+ // GoDaddy /v1/domains/purchase needs full contact JSON + signed consent.
+ // Implementing this safely requires Steve's stored contact + payment method.
+ // For now, refuse to call so we don't silently fail-open and bill him.
+ return {
+ success: false,
+ code: 'NOT_WIRED',
+ detail: 'GoDaddy direct-purchase needs full contact + consent payload + saved payment method. Wire via secrets-manager + GODADDY_PURCHASE_PAYLOAD env before use.',
+ };
+}
+
+async function main() {
+ const args = process.argv.slice(2);
+ const domain = args.find((a) => !a.startsWith('--'));
+ const confirm = args.includes('--confirm');
+
+ if (!domain) {
+ console.error('usage: node register.js <domain> [--confirm]');
+ console.error(' default = DRY-RUN. Pass --confirm to actually buy.');
+ process.exit(2);
+ }
+ if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z]{2,})+$/i.test(domain)) {
+ console.error(`invalid domain syntax: ${domain}`);
+ process.exit(2);
+ }
+
+ const backend = detectBackend();
+
+ console.log(`\n domain: ${domain}`);
+ console.log(` backend: ${backend || '(none — credentials missing)'}`);
+ console.log(` mode: ${confirm ? '\x1b[31mREAL PURCHASE\x1b[0m' : 'dry-run'}`);
+
+ if (!backend) {
+ console.error(`\n no registrar credentials found in env. add via the \`secrets\` skill:`);
+ console.error(` NAMESILO_API_KEY # cheapest, simplest`);
+ console.error(` NAMECHEAP_API_USER + NAMECHEAP_API_KEY # needs IP whitelist + prefund`);
+ console.error(` GODADDY_KEY + GODADDY_SECRET # needs payment-on-file`);
+ process.exit(3);
+ }
+
+ if (!confirm) {
+ console.log(`\n [DRY-RUN] would register ${domain} via ${backend}.`);
+ console.log(` pass --confirm to spend money. registration is NOT reversible.\n`);
+ return;
+ }
+
+ // Real call
+ let result;
+ const start = Date.now();
+ try {
+ if (backend === 'namesilo') result = await registerNamesilo(domain);
+ else if (backend === 'namecheap') result = await registerNamecheap(domain);
+ else if (backend === 'godaddy') result = await registerGodaddy(domain);
+ } catch (e) {
+ result = { success: false, code: 'EXCEPTION', detail: e.message };
+ }
+ const elapsed = Date.now() - start;
+
+ const rec = { ts: new Date().toISOString(), domain, backend, result, elapsedMs: elapsed };
+ fs.mkdirSync(DATA_DIR, { recursive: true });
+ fs.appendFileSync(LOG_FILE, JSON.stringify(rec) + '\n');
+
+ if (result.success) {
+ console.log(`\n \x1b[32m✓ REGISTERED ${domain}\x1b[0m (${elapsed}ms via ${backend})`);
+ console.log(` log: ${LOG_FILE}\n`);
+ } else {
+ console.log(`\n \x1b[31m✗ FAILED:\x1b[0m ${result.code || ''} — ${result.detail || ''}\n`);
+ process.exit(4);
+ }
+}
+
+main().catch((e) => { console.error('error:', e.message); process.exit(1); });
← c8f80e8 honeypot v2: DoH pre-check is ground-truth; 6-letter .com to
·
back to Domain Sniper
·
step 8: registrar-fingerprints.json + cross-ref in honeypot 7b38b4a →