DomainTwistex

DomainTwistex is a pure Elixir library for domain name permutation generation and typosquatting detection. It generates domain permutations, finds the registered ones with a fast two-stage DNS pipeline, and enriches each hit with DNS, HTTP/TLS, and WHOIS data. It returns raw data and leaves the judgment of what's malicious to an analyst or model.

Features

Installation

Add domaintwistex to your list of dependencies in mix.exs:

def deps do
  [
    {:domaintwistex, "~> 0.10.0"}
  ]
end

Prerequisites

No Rust toolchain required — permutation generation is pure Elixir.

Usage

Domain Analysis

# Full analysis — resolves original + all permutations
result = DomainTwistex.analyze("example.com")

# Returns:
%{
  domain: "example.com",
  original: %{fqdn: "example.com", resolvable: true, nameservers: [...], ...},
  permutations: [
    %{
      kind: "Homoglyph",
      fqdn: "xn--exmple-jta.com",
      unicode: "exàmple.com",
      registered: true,
      resolvable: true,
      mx_records: [%{priority: 10, server: "mail.xn--exmple-jta.com"}],
      server_response: %{http: %{...}, https: %{status_code: 200, title: "Sign in"}, tls: %{age_days: 3, ...}},
      whois: %{registrar: "...", creation_date: "2026-10-01T...", ...},
      timed_out: [],
      ...
    },
    ...
  ],
  stats: %{
    total: 3312, found: 42, resolvable: 37, mx: 12,
    wildcard_filtered: 5, dns_errors: 0, timeouts: 1, elapsed_ms: 9_876
  }
}

# With options
result = DomainTwistex.analyze("example.com",
  nameservers: ["1.1.1.1", "8.8.8.8"],
  dns_concurrency: 500,
  whois: false,
  kinds: [:homoglyph, :bitsquatting, :tld]
)

Streaming

"example.com"
|> DomainTwistex.analyze_stream()
|> Stream.filter(&(&1.mx_records != []))
|> Enum.each(&notify/1)

MX-Only Filter

# Returns only permutations with MX records (potential phishing)
result = DomainTwistex.analyze_mx("example.com")

Permutation Generation (No Resolution)

permutations = DomainTwistex.permutations("example.com")
# => [%{fqdn: "examplea.com", tld: "com", kind: "Addition"}, ...]

permutations = DomainTwistex.permutations("example.com", tlds: :all, faux_tld: true)

Distributed Scanning

Node.connect(:"node2@host")
result = DomainTwistex.analyze_distributed("example.com")
# Same shape as analyze/2; chunks from failed nodes are re-run locally

CLI

mix twist example.com
mix twist -c 100 --no-whois example.com
mix twist -n 1.1.1.1 -n 8.8.8.8 --dns-concurrency 500 example.com
mix twist --format json -o results.json example.com
mix twist -k homoglyph,bitsquatting example.com

Options

Option Default Description
max_concurrency max(System.schedulers_online() * 4, 16) Concurrent enrichments (stage 2)
dns_concurrency 200 Concurrent existence probes (stage 1)
timeout 15000 Enrichment budget per domain (ms)
dns_timeout 5000 Timeout per DNS query (ms)
http_timeout 5000 Timeout per HTTP/HTTPS request (ms)
retries 1 DNS retries on timeout/SERVFAIL
nameservers Cloudflare, Google, Quad9 unfiltered nil uses the system resolver. 40 in-flight probes per resolver
whois true WHOIS/RDAP lookups for registered hits (false is faster)
ordered false Return results in permutation order instead of grouped by kind

Permutation Options

Option Default Description
tlds :common :common (~150 curated), :all (~7K public suffixes), or a list
kinds all Only these kinds, e.g. [:homoglyph, "Tld"]
faux_tld false Include FauxTld permutations
double_vowel true Include DoubleVowelInsertion
vowel_shuffle false Include VowelShuffle (up to 625 entries)
priority_keywords_only false Only use high-value phishing keywords

Inspection Results

Each permutation result includes:

Field Type Description
kind string Permutation type (e.g., "Homoglyph", "Tld")
fqdn string ASCII domain name (punycode for IDNs)
unicode string Unicode form, only present for IDN permutations
tld string Public suffix
registered boolean Domain exists in DNS (NOERROR)
resolvable boolean Domain has A/AAAA records
cname string or nil CNAME target
ip_addresses [string] All resolved IPv4/IPv6 addresses
public_ips [string] Public addresses
internal_ips [string] Private/reserved addresses
ip_flags [atom] Flags like :localhost, :private_10, :cgnat, :ipv6_ula
mx_records [map] MX records sorted by priority
txt_records [string] TXT records
spf_records map or nil Parsed SPF (mechanisms, lookup count, providers, warnings); nil if none published
dmarc map DMARC policy
nameservers [string] Name servers
wildcard boolean Whether wildcard DNS is configured under the domain
server_response map %{http: ..., https: ..., tls: ...} — status, server, location, title, certificate
whois map or nil WHOIS data (registrar, dates); nil if disabled or the lookup failed
fuzzy map Similarity scores
timed_out [atom] Checks that didn't finish within the budget

Results where wildcard: true and public_ips: [] are filtered out, as are hits whose IPs match their TLD's registry wildcard.

Permutation Types

Kind Description
Addition Append a-z to domain
Bitsquatting Flip one bit in each character
Hyphenation Insert hyphens between characters
HyphenationTldBoundary Hyphenate multi-part TLD boundary
Insertion Insert adjacent keyboard characters
Omission Remove each character
Repetition Double each character
Replacement Replace with adjacent keyboard characters
Subdomain Insert dots to create subdomains
Transposition Swap adjacent characters
VowelSwap Replace vowels with other vowels
VowelShuffle Combinatorial vowel replacement (opt-in)
DoubleVowelInsertion Insert vowels between vowel pairs
Keyword Prepend/append common keywords
Tld Replace TLD with common (or all) TLDs
FauxTld TLD-like strings appended to the name (opt-in)
Mapped Character substitutions (l→1, o→0, etc.) at each occurrence
Homoglyph Unicode look-alike character substitution

Modules

License

BSD-3-Clause

Acknowledgments

Permutation algorithms inspired by twistrs. This library was originally a Rust NIF wrapper around twistrs and has been rewritten as pure Elixir.