Skip to content

Domain Discovery

Domain discovery (stage 1, domain_enumeration) turns a brand’s owned domains and keywords into candidate lookalike domains. It is powered by dnstwist-based permutation in domain_intel/domain_generation.py.

  • Owned domains — the exact domains the brand controls (seeds).
  • Keywords — user-provided plus AI-inferred brand keywords.

The domain_generation module produces variations across several fuzzers:

Fuzzer Example
Typosquat oktoboo.co (dropped character)
Homoglyph oktоboot.com (lookalike unicode)
TLD abuse oktoboot.org, oktoboot.net
Keyword variants oktoboot-wallet.com, oktobootsupport.com
Character insertion / transposition okktoboot.com, oktbooot.com

Results are deduplicated (dedupe_findings) and collapsed across target domains so the same lookalike found for several owned domains appears once (_dedupe_across_targets).

The phase returns (results, total_permutations) where results maps each owned domain to its list of DomainFinding objects, and total_permutations is the count of generated variations (used for the processed/total progress display).

def domain_enumeration_phase(
domains: list[str],
keywords: list[str],
cancel_event=None,
scan_id=None,
) -> tuple[dict[str, list[DomainFinding]], int]
  • The loop checks the cancel event per domain; a cancelled scan stops generating new permutations.
  • Findings carry fuzzer and original_domain metadata so the dashboard can show which family and source produced them.

Outside scans, each owned domain in the brand profile triggers EnrichDomainJobPOST /v1/domain/enrich, which builds a domain_profiles snapshot. This snapshot is included in the scan-start payload so the pipeline can enrich discovered lookalikes against a fresh baseline.

  • Discovery is fully deterministic and offline (no external calls) — only enrichment and scoring touch the network.
  • Tests stub dnstwist.run (see stub_dnstwist fixture in services/tests/conftest.py).