Skip to main content

Discover, monitor, score, and cluster brand-impersonation domains — a layered, pip-extras toolkit for the lookalike-domain lifecycle, from typo-squat generation to actor-campaign clustering.

Project description

domainflow

The lookalike-domain lifecycle, as a toolkit you can pip install. Generate the typo-squat space for a brand, find which candidates are real (Certificate Transparency + WHOIS), score how weaponized they are, and cluster the findings that share an actor's infrastructure into campaigns — so a coordinated wave of impersonations reads as one thing, not fifty scattered alerts.

Offline-safe core, model-agnostic, no vendor lock-in.

from domainflow import generate_lookalikes, cluster_campaigns

# 1. Generate the impersonation space for a brand
for la in generate_lookalikes("acme.com"):
    print(la.fuzzer, la.domain)        # homoglyph ecme.com, tld-swap acme.xyz, …

# 2. (after you've enriched some findings) cluster by shared infrastructure
findings = [
    {"domain": "acme-login.com", "ip_addresses": ["5.5.5.5"], "registrant_org": "Foo Holdings"},
    {"domain": "acme-secure.com", "ip_addresses": ["5.5.5.5"], "registrant_org": "Foo Holdings"},
    {"domain": "acme-verify.net", "ip_addresses": ["9.9.9.9"], "registrant_org": "foo holdings"},
]
for c in cluster_campaigns(findings):
    print(c.size, [p.value for p in c.pivots], c.domains)
    # 3 ['foo holdings', '5.5.5.5'] ['acme-login.com', 'acme-secure.com', 'acme-verify.net']

Why

Plenty of tools generate typo-squats (dnstwist) or stream Certificate Transparency. What's missing is the part that turns a pile of lookalike alerts into something a human can act on: which of these belong to the same actor?

domainflow's flagship is the clustering. The hard part isn't grouping by shared IP/registrant/nameserver — it's not over-grouping. Phishing kits overwhelmingly reuse a few bulk registrars, Let's Encrypt, and Cloudflare, so a naive join collapses every unrelated domain into one giant blob. domainflow only joins on discriminating pivots and suppresses values that are too common to mean anything — so a cluster reflects a genuinely linked set of registrations.

Install

pip install domainflow                 # core: discover + cluster (stdlib only)
pip install domainflow[ct]             # + Certificate Transparency (crt.sh)
pip install domainflow[whois]          # + WHOIS snapshot/diff
pip install domainflow[score]          # + weaponization signals (page + DNS)
pip install domainflow[all]            # everything

The four layers

discover — generate the impersonation space

from domainflow import generate_lookalikes, FUZZERS

generate_lookalikes("acme.com")                          # high-signal default set
generate_lookalikes("acme.com", fuzzers=["homoglyph"])   # one technique
generate_lookalikes("acme.com", fuzzers=list(FUZZERS) + ["dictionary-combo"])  # everything
generate_lookalikes("acme.com", keywords=["claims", "benefits"])  # custom brand+keyword

Fuzzers: homoglyph, omission, repetition, transposition, replacement, insertion, vowel-swap, hyphenation, addition, bitsquatting, tld-swap, dictionary-combo, industry-tld, split-brand.

monitor — find which candidates are real

from domainflow.monitor import ct, whois

ct.discovered_domains("acme")           # domains from new certs mentioning "acme"
snap = whois.snapshot("acme-login.com") # normalised registration facts
whois.diff(previous_snap, snap)         # [{field: 'name_servers', severity: 'high', …}]

score — is it weaponized?

from domainflow import score

r = score.score("acme-login.com", brand="acme")
print(r["tier"])                        # P1 (live login clone) … P4 (parked/benign)

# bring your own LLM for a sharper verdict — any OpenAI-compatible endpoint
llm = score.openai_verdict("https://api.openai.com/v1", api_key, "gpt-4o-mini")
score.score("acme-login.com", brand="acme", llm=llm)

Offline, the tier comes from a deterministic heuristic over page + MX/SPF/DMARC signals. No model required.

cluster — group findings into campaigns

from domainflow import cluster_campaigns

campaigns = cluster_campaigns(findings)   # list[Finding] or list[dict]
for c in campaigns:                       # sorted active-first, then largest-first
    print(c.id, c.size, c.brands, [(p.type, p.value) for p in c.pivots])

A Finding needs only a domain; populate ip_addresses, registrant_org, nameservers (and optionally active/contained) for richer clustering. Loose dicts are coerced, tolerating common key aliases (dns_a, name_servers, issuer, registrant, …).

CLI

domainflow discover acme.com
domainflow ct acme --domains
domainflow whois acme-login.com
domainflow score acme-login.com --brand acme
domainflow cluster findings.json

Design notes

  • Core is stdlib-only. discover and cluster have zero dependencies; network layers are opt-in extras.
  • No storage imposed. Everything is plain dataclasses with to_dict/from_dict. Bring your own database.
  • Model-agnostic. Scoring works offline; the LLM path is a callable you supply.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

domainflow-0.1.0.tar.gz (22.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

domainflow-0.1.0-py3-none-any.whl (24.5 kB view details)

Uploaded Python 3

File details

Details for the file domainflow-0.1.0.tar.gz.

File metadata

  • Download URL: domainflow-0.1.0.tar.gz
  • Upload date:
  • Size: 22.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for domainflow-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9549236d858b5db5307db324c0e94a5bbf4bff0810f578bdde1b656b72752fe4
MD5 1ec48ea2fbe9695273f0ffe832e2bc7c
BLAKE2b-256 a2cdac738cc4a89b37ff4f74f97abdb2e83534708884fe45ca8134bcfc2ad081

See more details on using hashes here.

Provenance

The following attestation bundles were made for domainflow-0.1.0.tar.gz:

Publisher: release.yml on vinayvobbili/domainflow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file domainflow-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: domainflow-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 24.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for domainflow-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ff314d25076dd1bb9739eaca5efba82ebc3d8c185e14b9f0f16ecf3599ad0d44
MD5 4e0d0ec78cfc1497403ca2f34083b735
BLAKE2b-256 fa64b89e21fa64a7792b980df4f6c96b6d1091dbfc2092791ba991fede5517e0

See more details on using hashes here.

Provenance

The following attestation bundles were made for domainflow-0.1.0-py3-none-any.whl:

Publisher: release.yml on vinayvobbili/domainflow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page