Skip to content
WhisperGraph
Skip navigation

whisper.variants() — Lookalike Generation

Generate typosquat and homoglyph lookalikes of a domain, keep only the registered ones, and pivot the hits straight into a threat verdict.

Published Last updated

On this page (5)

whisper.variants() — Lookalike Generation Documentation

whisper.variants() generates lookalike domains for a name you give it and, by default, returns only the ones registered in the graph. One call replaces the usual pipeline of an offline typosquat generator plus a bulk registration check. Because the generation and the existence check happen where the rest of the investigation lives, you can chain the hits straight into DNS resolution, routing, and threat-feed context in the same query.

The procedure takes the domain as its first argument. By default it filters to variants that exist as HOSTNAME nodes; pass a node label as the second argument to check a different node type, or false as the filter argument to return every generated variant, registered or not. Each row carries variant, method, exists, confidence and confidenceLabel.

Generation strategies

The domain is run through a set of generation algorithms: character omission, repetition, transposition, keyboard-adjacent replacement and insertion, homoglyph substitution, bitsquatting, TLD swap, and others. The method column names the strategy behind each row (OMISSION, TRANSPOSITION, HOMOGLYPH, BITSQUATTING, ...), and confidence ranks how plausible the variant is as a lookalike, so you can sort the shortlist before triaging it.

cypher · runnablegraph.whisper.securitySign in to run
// Registered lookalikes of paypal.com, highest-confidence first
CALL whisper.variants("paypal.com")
YIELD variant, method, exists, confidence, confidenceLabel
WHERE exists
RETURN variant, method, confidence, confidenceLabel
ORDER BY confidence DESC
LIMIT 25

json
[
  {"variant": "paypa1.com", "method": "HOMOGLYPH", "confidence": 1.0, "confidenceLabel": "high"},
  {"variant": "paypai.com", "method": "HOMOGLYPH", "confidence": 1.0, "confidenceLabel": "high"},
  {"variant": "paypan.com", "method": "BITSQUATTING", "confidence": 1.0, "confidenceLabel": "high"},
  {"variant": "paytal.com", "method": "BITSQUATTING", "confidence": 1.0, "confidenceLabel": "high"}
]

Captured 2026-09-02, abridged. The registered set moves as names are registered and dropped.

What the exists flag means

exists: true means the variant is registered. It does not mean the variant is malicious. A parked typosquat and a live phishing page both show up as registered, so treat every hit as a candidate that still needs triage.

The unregistered side is useful too. Pass false as the filter argument and the full generated space comes back, which is the list worth reviewing for defensive registration before someone else stands one up.

cypher · runnablegraph.whisper.securitySign in to run
CALL whisper.variants("paypal.com", false)
YIELD variant, method, exists
WHERE NOT exists
RETURN variant, method
LIMIT 10

In expression position

whisper.variants is also a function, so it works inside an expression as well as after CALL: size(whisper.variants("paypal.com")) counts the generated space, and slicing the list gives you the names without a YIELD. The function form returns the variant names only; use CALL when you need method, exists or confidence.

cypher · runnablegraph.whisper.securitySign in to run
RETURN size(whisper.variants("paypal.com")) AS generated,
       whisper.variants("paypal.com")[0..5] AS sample

Read coverage before band. Only known-clean — coverage: known-clean. In coverage, no malicious evidence. licenses the word "clean"; no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. means unknown, which is a different thing again; malicious-evidenced — coverage: malicious-evidenced. In coverage, with positive evidence of malice. and ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways. mean there is evidence, whatever the band says. Full contract: Coverage — what we looked at.

Pivot the hits to a verdict

The standard triage read chains the generator into resolution and the reconciled verdict properties in one pass: which registered lookalikes are live, where they resolve, and which already carry a blocking verdict.

cypher · runnablegraph.whisper.securitySign in to run
// Registered variants → where they resolve → reconciled verdict, all at once
CALL whisper.variants("paypal.com")
YIELD variant, method, exists
WHERE exists
WITH variant, method LIMIT 50
MATCH (h:HOSTNAME {name: variant})-[:RESOLVES_TO]->(ip:IPV4)
RETURN variant, method, ip.name AS resolves_to,
       ip.verdictLevel AS ip_verdict, ip.verdictBlocking AS blocking
ORDER BY blocking DESC, ip_verdict DESC
LIMIT 25

For any single suspect, explain() returns the full evidence chain: score, level, the contributing feeds with their weights, and first/last-seen timestamps you can paste into a takedown ticket.

cypher · runnablegraph.whisper.securitySign in to run
CALL explain("paypa1.com")
YIELD indicator, found, score, level, explanation, sources
RETURN indicator, found, score, level, explanation, sources

Keep the coverage caveat in mind: a NONE level is not proof the graph has never seen the name. Run the call above and paypa1.com returns NONE with a populated sources array, so read found, sources and explanation together, and ask whisper.assess the coverage question. Either way a lookalike can still be a brand-new phishing page. Fresh lookalikes routinely beat feed coverage, so a variant that resolves but has no verdict yet is often the one to look at first.

Where it fits

  • The Brand Protection use case and its Lookalike Hunting recipes build the full loop on this procedure: surface scans, cluster expansion through co-hosting and shared nameservers, and certificate-transparency monitoring.
  • To run the generate-and-triage loop in the browser without writing Cypher, use the Typosquat Scanner workflow.
  • On the AI side, the same capability is available over MCP: call the procedure through the query tool, or run the typosquat workflow with run_workflow; see the MCP reference.
  • For the other procedures and when to prefer them over a hand-written traversal, see the procedures overview.