Skip to main content

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.

score is point-in-time. To catch the strongest tell — a lookalike that sat dormant for weeks and then suddenly went live (deliberate, aged infrastructure) — diff this scan against the last one:

prev = load_last_score("acme-login.com")        # whatever you stored last scan
now = score.score("acme-login.com", brand="acme")

t = score.transition(prev, now, dormant_days=92)
if t["became_active"]:
    print(t["recommended_tier"], t["rationale"])  # e.g. P2 → escalated to P1

On a dormant→live flip, transition recommends a one-notch escalation above the point-in-time tier — a freshly-activated aged domain outranks a brand-new live one of the same tier. It also reports went_dormant, escalated/deescalated, and is pure (no network), so it works without the score extra.

Retention tip: don't expire dormant lookalikes. The quiet domain at day 31 is exactly the one this catches when it activates — archive, don't delete, and keep its first_seen so you can pass dormant_days.

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

Metadata

Release files for domainflow 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for domainflow 0.1.1
File Size Uploaded Provenance Actions
domainflow-0.1.1.tar.gz 24.5 kB With provenance Details for domainflow-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for domainflow 0.1.1
File Interpreter ABI Platform Actions
domainflow-0.1.1-py3-none-any.whl Python 3 none any Details for domainflow-0.1.1-py3-none-any.whl

Total release size: 51.1 kB

Release files / domainflow-0.1.1.tar.gz

Download URL domainflow-0.1.1.tar.gz
Size 24.5 kB
Tags Source
SHA-256 checksum
How to use checksums
f032984af0a9337a10d50abfc448b51fc0e339a0fc399f217d35e1d6082c580e
BLAKE2b-256 checksum
How to use checksums
63fe1d8520142f3e2caa36737d046c3962413e4b0993e1fd9e3e64e3ce4433b0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Release files / domainflow-0.1.1-py3-none-any.whl

Download URL domainflow-0.1.1-py3-none-any.whl
Size 26.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2a5f637fd3b607b968425855f5ace1a88568fb7332f6e32210576143c0d9c608
BLAKE2b-256 checksum
How to use checksums
f7de1594640e55339ed6f4ec8e124091a07dd1fde5e04cb15b9b2f093eadecab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Security for domainflow 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for domainflow-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page