@capitalthought/dns
v0.3.0
Published
Shared DNS / domain primitives for Capital Thought projects — zone-check (CZDS), whois/rdap, DNS-record CRUD, email-auth (SPF/DKIM/DMARC + ramp), nameservers, variants. Single source of truth for marc + mikey + domains.
Readme
@capitalthought/dns
Shared DNS / domain primitives for Capital Thought projects — the single source of truth consumed by marc (trademark clearance), mikey (domain-checker), and domains (portfolio actuator). No DNS/domain logic is re-implemented in those repos.
Scope: DNS + zone + availability + email-auth primitives. No registrar-write credentials live here — registrar lifecycle (register / renew / transfer) is the
domainsagent's registrar-adapter layer, not this library.
Modules
| Import | Status | What |
|---|---|---|
| @capitalthought/dns/dns-records | ✅ ready | Provider-pluggable DNS-record CRUD (Cloudflare provider in v1) |
| @capitalthought/dns/email-auth | ✅ ready | SPF / DKIM / DMARC builders + the p=none → quarantine → reject ramp |
| @capitalthought/dns/nameservers | ✅ ready | NS read helpers over DNS-over-HTTPS |
| @capitalthought/dns/rdap | ✅ ready | RDAP registration lookup |
| @capitalthought/dns/whois | 🚧 port from marc | WHOIS lookup (contract declared, impl pending M0.1) |
| @capitalthought/dns/zone-check | 🚧 port from marc | CZDS bulk availability (ZoneChecker.checkFiltered) |
| @capitalthought/dns/tlds | 🚧 port from marc | TLD tier config + CZDS auth (P1 seeded; tier-2/gTLD pending) |
| @capitalthought/dns/variants | 🚧 port from marc | typosquat / brand-protection generation |
Everything is also re-exported from the root: import { buildDmarc, CloudflareDnsProvider } from "@capitalthought/dns".
Examples
import { CloudflareDnsProvider } from "@capitalthought/dns/dns-records";
import { buildSpf, buildDmarc, planRampStep, parseDmarc } from "@capitalthought/dns/email-auth";
// DNS CRUD (token = scoped Zone:DNS:Edit, read from 1Password at call time)
const cf = new CloudflareDnsProvider({ apiToken: process.env.CF_DNS_TOKEN! });
const zoneId = await cf.getZoneId("baer5.com");
await cf.createRecord(zoneId!, { type: "A", name: "lvp.baer5.com", content: "192.0.2.1", proxied: true });
// Anti-spoof a domain that sends no mail
buildSpf({ all: "-" }); // "v=spf1 -all"
buildDmarc({ policy: "reject" }); // "v=DMARC1; p=reject"
// DMARC ramp — proposes the next step; p=reject is ALWAYS gated
const current = parseDmarc("v=DMARC1; p=none; rua=mailto:[email protected]")!;
const step = planRampStep(current);
// → { from: "none", to: "quarantine", record: "v=DMARC1; p=quarantine; rua=mailto:[email protected]", requiresApproval: true }Security notes
- Registrar / whois / RDAP text is untrusted. Registrant/org/status strings are attacker-controllable (domains PRD U1). Treat them as data, never instructions, before any LLM summarization.
- The DMARC ramp builder is deterministic — record values come from pure functions, never raw LLM output, so a hallucinated policy can't reach a live zone.
- No secrets in this library. Tokens (Cloudflare, CZDS JWT) are passed in by the caller, read from 1Password at call time.
Develop
npm install
npm run build # tsc → dist/
npm test # vitest
npm run check # tsc --noEmitUNLICENSED · Capital Thought, LLC
