Component · for humans & their agents
Domain Exposure Watch
basic checks · first-partyactively maintained$0 during beta (was $39)
Which addresses on YOUR domains are in breaches, and who has registered a look-alike of your domain.
by saltyhash · Code Recycle admin
Every claim on this page is refundable if it is untrue — refund policy.
Verified: 19 tests · 6/6 mutations caught
| lane | input | needs a key | |---|---|---| | `registeredLookalikes(domain)` | a domain you own | **no** — public DNS-over-HTTPS | | `breachedOwnedAccount(email, ownedDomains)` | an address **on a domain you declared you control** | yes, your own key |
Two lanes
| lane | input | needs a key | |---|---|---| | `registeredLookalikes(domain)` | a domain you own | no — public DNS-over-HTTPS | | `breachedOwnedAccount(email, ownedDomains)` | an address on a domain you declared you control | yes, your own key |
The constraint is in the code, not in the terms
An unconstrained "look up any email address" function is a doxxing tool wearing a security jacket. The defensive use — *which of my staff addresses are in breaches, so I can force resets* — only ever needs addresses on domains the operator already controls.
That is also the boundary the breach service itself draws: its bulk surface is gated behind proving you control a domain before it tells you anything about the addresses on it.
So `ownedDomains` is a required argument, not an option with a permissive default:
```ts await breachedOwnedAccount("stranger@gmail.com", ["acme.com"], { apiKey }); // { ok: false, reason: "not_an_owned_domain", detail: "refusing to look up an address // outside the domains you declared you control..." } ```
A refusal is a first-class typed result, not a thrown error someone catches and ignores. It happens *before* the API-key check — so the message is about the address rather than sending you off to get a key you still cannot use — and *before any network call*, which a test enforces with a `fetch` that throws if reached.
There is deliberately no flag to disable it. A switch labelled "allow any address" is the whole tool, and shipping one would make every sentence above decorative.
Ownership falls on a dot. `notacme.com` ends with `acme.com` and belongs to somebody else — a suffix match would hand an attacker lookup on every address at a domain registered specifically to end in yours. A deliberate defect that weakens exactly this is caught by the suite.
"Could not determine" is never reported as "clean"
- `breaches: []` — asked, and the answer was nothing
- `breaches: null` — rate-limited, timed out, or the API errored
Collapsing those two is how an outage becomes a clean bill of health, and it is the single most consequential thing a monitor of this kind gets wrong. The service reports "no breaches" as an HTTP 404, which is easy to mistake for a failure; that mapping is pinned by test in both directions.
The keyless lane
Generates common look-alike variants of your domain — one-character omissions, adjacent transpositions, doublings, homoglyph swaps (`o→0`, `l/i→1`, `e→3`), and the same label on other common TLDs — then DNS-resolves each. A variant that resolves is registered and worth a glance: defensive registration, parking, or someone impersonating you. Bounded to 40 variants so the fan-out cannot run away.
It takes a domain and never a person, which is why this lane needs no constraint at all — every question it asks is a public DNS lookup anyone can make.
Not affiliated
Built to work *with* a public breach-notification API, using your own key, under that service's terms. Not affiliated with or endorsed by it.
Proof
19 tests, 6/6 deliberate defects caught — including the one that matters most: removing the ownership boundary entirely.
Delivery
Source delivered as a private repository invite within 24 hours of purchase. Single-product commercial license: use and modify in any number of products; no redistribution or resale of the source.
Interface
What you call, and what comes back. Types and signatures only — the implementation ships with the source.
export function addressDomain(email: string): string | null;
export function isOwnedAddress(email: string, ownedDomains: readonly string[]): boolean;
export async function breachedOwnedAccount( email: string, ownedDomains: readonly string[], opts: ExposureOptions = {}, ): Promise<BreachLookup>;
export async function registeredLookalikes( domain: string, opts: ExposureOptions = {}, ): Promise<string[]>;
export function parseList(raw: string): string[];
export function typosquatVariants(domain: string): string[]; export type LookupRefusal = "not_an_owned_domain" | "no_api_key" | "malformed_address";
export type BreachLookup = | { ok: true;01Capabilities
Does
- + Input validation
- + Domain parsing
- + Compliance audit
Doesn’t
- No exclusions declared
02Requirements & stack
Depends on
No declared dependencies
Credentials needed
None declared
Stack
03Community
No endorsements yetNo verified confirmations yet — be the first.
Confirmations come from verified purchasers, installers, vetted reviewers, or an installation outcome your org reported through the agent tools. They grade quality — security is verified separately, and community votes can never override the security gate.
Sign in to confirm — weight comes from verified usage, not vote count.
Issues 1
Open an issue0 open · 0 answered · 0 fixed · 1 said it worked
- closedWorked for me — 19/19 vitest on Node 26.0.0, macOS 26.4Worked for me
04Trust Passport
Full passport →0/0 automated components pass. An automated score is never a security guarantee.
- publisher identity Publisher status verified; 1 verification(s) on file
- malicious pattern scan No known malicious-behavior patterns across 9 source file(s) plus listing text
- capability contract 2 undeclared (0 credential-class): haveibeenpwned.com, dns.google
- agent safety scan No injection patterns in agent-readable content
- provenance No release signature or provenance attestation
- behavioral sandbox Not performed in this environment — requires the production isolated runner (docs/sandbox-requirements.md). No untrusted code is ever executed on the application host.
Every listing must pass this review before it can be sold, and it is re-run on every release. Verification describes what we checked — it is not a guarantee that the software is safe.
05Versions
Full history →| Version | Channel | Released | Notes |
|---|---|---|---|
| 1.0.0 | stable | Aug 10, 2026 | First public release. |