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.
discoverandclusterhave 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9549236d858b5db5307db324c0e94a5bbf4bff0810f578bdde1b656b72752fe4
|
|
| MD5 |
1ec48ea2fbe9695273f0ffe832e2bc7c
|
|
| BLAKE2b-256 |
a2cdac738cc4a89b37ff4f74f97abdb2e83534708884fe45ca8134bcfc2ad081
|
Provenance
The following attestation bundles were made for domainflow-0.1.0.tar.gz:
Publisher:
release.yml on vinayvobbili/domainflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
domainflow-0.1.0.tar.gz -
Subject digest:
9549236d858b5db5307db324c0e94a5bbf4bff0810f578bdde1b656b72752fe4 - Sigstore transparency entry: 1809398150
- Sigstore integration time:
-
Permalink:
vinayvobbili/domainflow@907f498abe55b2a910d4f4a19f56569c929d2c90 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vinayvobbili
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@907f498abe55b2a910d4f4a19f56569c929d2c90 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff314d25076dd1bb9739eaca5efba82ebc3d8c185e14b9f0f16ecf3599ad0d44
|
|
| MD5 |
4e0d0ec78cfc1497403ca2f34083b735
|
|
| BLAKE2b-256 |
fa64b89e21fa64a7792b980df4f6c96b6d1091dbfc2092791ba991fede5517e0
|
Provenance
The following attestation bundles were made for domainflow-0.1.0-py3-none-any.whl:
Publisher:
release.yml on vinayvobbili/domainflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
domainflow-0.1.0-py3-none-any.whl -
Subject digest:
ff314d25076dd1bb9739eaca5efba82ebc3d8c185e14b9f0f16ecf3599ad0d44 - Sigstore transparency entry: 1809398153
- Sigstore integration time:
-
Permalink:
vinayvobbili/domainflow@907f498abe55b2a910d4f4a19f56569c929d2c90 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vinayvobbili
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@907f498abe55b2a910d4f4a19f56569c929d2c90 -
Trigger Event:
push
-
Statement type: