Cypher Query Cookbook
Ready-to-use Cypher query recipes organized by role — SOC analysts, threat intel, pentesters, brand protection, DNS/email, BGP, compliance, insurance, law enforcement, and researchers.
Cypher Query Cookbook Documentation
Ready-to-run Cypher recipes organized by what you're trying to accomplish. Each recipe is self-contained -- swap in your own indicators and run against the Cypher API. For language syntax and schema details, see the Cypher Language Reference.
Contents
- Getting Started
- For SOC Analysts and Incident Responders
- For Threat Intelligence Analysts
- For Penetration Testers and Red Teams
- For Brand Protection and Anti-Phishing Teams
- For DNS and Email Security Engineers
- For Network and BGP Security Engineers
- For Compliance and Risk Assessment Teams
- For Cyber Insurance and Third-Party Risk
- For Law Enforcement and Cybercrime Investigators
- For Security Researchers and Academics
- Cross-Cutting Recipes
- Quick Reference
Getting Started
The API
All queries go to a single endpoint:
POST https://graph.whisper.security/api/query
Content-Type: application/json
X-API-Key: <your-key> (optional — anonymous access is available)
{"query": "MATCH (h:HOSTNAME {name: 'example.com'}) RETURN h.name"}
Every response has three fields:
{
"columns": ["h.name"],
"rows": [{"h.name": "example.com"}],
"statistics": {"rowCount": 1, "executionTimeMs": 0}
}
executionTimeMs is server-side processing time only. Network latency adds roughly 130ms per request.
Your First Query
Check whether a hostname exists in the graph:
-- Confirm a hostname is indexed
MATCH (h:HOSTNAME {name: "google.com"}) RETURN h.name LIMIT 1
Sample output (from live validation):
[{"h.name": "google.com"}]
Tip: Always anchor your starting point with
{name: "value"}. Queries that scan all nodes of a label without an anchor will time out on billion-node labels.
Resolve a Hostname to Its IP Addresses
-- DNS A record lookup
MATCH (h:HOSTNAME {name: "www.google.com"})-[:RESOLVES_TO]->(ip:IPV4)
RETURN h.name, ip.name LIMIT 20
Sample output:
[
{"h.name": "www.google.com", "ip.name": "142.250.64.100"},
{"h.name": "www.google.com", "ip.name": "142.251.150.119"},
{"h.name": "www.google.com", "ip.name": "142.251.151.119"}
]
Tip: Most domains resolve to a handful of IPs. Use
LIMIT 20as a default safety cap — some CDN hostnames can resolve to dozens of addresses.
Check Your Plan and Usage
-- View your current quota and plan tier
CALL whisper.quota()
Sample output:
[
{"key": "plan", "value": "PROFESSIONAL"},
{"key": "isAnonymous", "value": false},
{"key": "queriesRemaining", "value": 9850}
]
For SOC Analysts and Incident Responders
You've received an alert and need to triage fast. These recipes take you from a raw indicator to a full infrastructure picture without leaving your terminal.
Quick Triage
Trace an IP to Its Network Owner
Your SIEM flagged an outbound connection to an unfamiliar IP. Before escalating, you need to know who owns it and what network it belongs to.
-- Full BGP chain: IP -> announced prefix -> ASN -> network name
MATCH (ip:IPV4 {name: "104.16.132.229"})
-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)
-[:ROUTES]->(a:ASN)
-[:HAS_NAME]->(n:ASN_NAME)
RETURN ip.name AS ip, ap.name AS prefix, a.name AS asn, n.name AS network
Sample output:
[{"ip": "104.16.132.229", "prefix": "104.16.128.0/20", "asn": "AS13335", "network": "CLOUDFLARENET - Cloudflare, Inc."}]
Tip:
ANNOUNCED_BYresolves BGP routing data at query time, so you always get current routing information. For IPs where BGP data is unavailable,BELONGS_TOgives the registered allocation block instead.
Check If an IP Is Threat-Listed
You have a suspicious IP and need to know whether any threat intelligence feeds track it.
-- Check which threat feeds list this IP
MATCH (ip:IPV4 {name: "185.220.101.1"})-[:LISTED_IN]->(f:FEED_SOURCE)
RETURN ip.name, f.name
Sample output:
[
{"ip.name": "185.220.101.1", "f.name": "Dan Tor Exit"},
{"ip.name": "185.220.101.1", "f.name": "FireHOL Level 2"},
{"ip.name": "185.220.101.1", "f.name": "IPsum"},
{"ip.name": "185.220.101.1", "f.name": "Tor Exit Nodes"},
{"ip.name": "185.220.101.1", "f.name": "Spamhaus DROP"},
{"ip.name": "185.220.101.1", "f.name": "FireHOL Anonymous"}
]
Tip: The graph indexes 40 threat feeds. If the result is empty, the IP is not currently listed — but also run
CALL explain()(see below) for a composite score that accounts for network-level reputation.
Get a Full Threat Assessment
You want a scored, human-readable verdict on an IP, domain, ASN, or CIDR block.
-- Composite threat score and explanation
CALL explain("185.220.101.1")
Sample output:
[{
"indicator": "185.220.101.1",
"type": "ip",
"found": true,
"score": 21.55,
"level": "INFO",
"explanation": "185.220.101.1 is listed in 6 threat feed(s). Score 21.6 (Informational - minimal risk).",
"factors": [
"Listed in 6 source(s) with combined weight 8.20",
"Base score: 8.20 × log₂(6 + 1) = 23.19",
"Recency boost: ×1.2 (last seen just now)"
]
}]
Tip:
CALL explain()works on IPs, domains, ASNs (AS13335), and CIDR ranges (185.220.101.0/24). For network-level assessments, pass the CIDR — it scores the block's listed-IP density.
Deep Dive Investigation
Reverse DNS: What Domains Point Here?
You have an IP from an alert and want to know what else is hosted there.
-- All domains currently resolving to this IP
MATCH (ip:IPV4 {name: "104.16.132.229"})<-[:RESOLVES_TO]-(h:HOSTNAME)
RETURN h.name LIMIT 20
Sample output:
[
{"h.name": "menuchin.app"},
{"h.name": "www.menuchin.app"},
{"h.name": "qapy.com.ar"},
{"h.name": "c-cloudflare-com.4i.am"}
]
Tip: Shared hosting is normal for CDN IPs — a single Cloudflare IP can host thousands of domains. If co-hosted count (see next recipe) is over a few hundred, the IP is likely shared infrastructure rather than a dedicated host.
Count Co-Hosted Domains
Before pivoting on all domains sharing an IP, check how many there are.
-- How many domains share this IP?
MATCH (ip:IPV4 {name: "104.16.132.229"})<-[:RESOLVES_TO]-(h:HOSTNAME)
RETURN count(h) AS cohosted
Sample output:
[{"cohosted": 1491}]
Tip: A count over 500 usually means shared CDN infrastructure. A count under 20 is more interesting for attribution — those domains are likely managed by the same operator.
Look Up GeoIP Location
You need to know the physical location of an IP for a geo-restriction check or incident report.
-- GeoIP city and country for an IP
MATCH (ip:IPV4 {name: "109.111.100.154"})
-[:LOCATED_IN]->(city:CITY)
-[:HAS_COUNTRY]->(co:COUNTRY)
RETURN DISTINCT ip.name, city.name AS city, co.name AS country
Sample output:
[{"ip.name": "109.111.100.154", "city": "Andorra la Vella, AD", "country": "AD"}]
Tip: Well-known anycast IPs (Google, Cloudflare DNS resolvers) may not return city-level GeoIP data because they're served from many locations simultaneously. For those, use the BGP routing chain:
IP -[:ANNOUNCED_BY]-> AP -[:HAS_COUNTRY]-> COUNTRYto get the network's registered country.
IP Geolocation via BGP Country
When GeoIP data is unavailable, get the country from the network's routing allocation.
-- Country via BGP prefix allocation
MATCH (ip:IPV4 {name: "8.8.8.8"})
-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)
-[:HAS_COUNTRY]->(co:COUNTRY)
RETURN ip.name, ap.name AS prefix, co.name AS country
Sample output:
[{"ip.name": "8.8.8.8", "prefix": "8.8.8.0/24", "country": "US"}]
Tip: This approach works even when
LOCATED_INreturns nothing. The country reflects where the announcing network is registered, not the physical server location.
Quick WHOIS Check
You need registration details for a suspicious domain — registrar, contact emails, and phone numbers.
-- WHOIS registration profile for a domain
MATCH (h:HOSTNAME {name: "cloudflare.com"})
OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR)
OPTIONAL MATCH (h)-[:HAS_EMAIL]->(e:EMAIL)
OPTIONAL MATCH (h)-[:HAS_PHONE]->(p:PHONE)
RETURN h.name,
collect(DISTINCT r.name) AS registrars,
collect(DISTINCT e.name) AS emails,
collect(DISTINCT p.name) AS phones
Sample output:
[{
"h.name": "cloudflare.com",
"registrars": ["iana:1910"],
"emails": ["domains@cloudflare.com", "noreply@data-protected.net"],
"phones": ["+10000000000", "+16503198930"]
}]
Tip: Use
OPTIONAL MATCHfor WHOIS fields — not every domain has every field populated. A plainMATCHwould return no results for partially-registered domains.
Who Controls DNS?
Find the authoritative nameservers for a domain.
-- Authoritative nameservers for a domain
MATCH (ns:HOSTNAME)-[:NAMESERVER_FOR]->(h:HOSTNAME {name: "google.com"})
RETURN ns.name LIMIT 10
Sample output:
[
{"ns.name": "ns1.google.com"},
{"ns.name": "ns2.google.com"},
{"ns.name": "ns3.google.com"},
{"ns.name": "ns4.google.com"}
]
Tip: The nameserver that controls DNS for a domain often tells you who's managing the infrastructure. Suspicious domains using free DNS providers or bulletproof hosting nameservers are worth flagging.
Evidence Collection
Full Infrastructure Trace
You need to document the complete path from domain to network owner for an incident report.
-- Full 5-hop chain: domain -> IP -> BGP prefix -> ASN -> network name
MATCH (h:HOSTNAME {name: "cloudflare.com"})
-[:RESOLVES_TO]->(ip:IPV4)
-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)
-[:ROUTES]->(a:ASN)
-[:HAS_NAME]->(n:ASN_NAME)
RETURN h.name AS host, ip.name AS ip, ap.name AS prefix,
a.name AS asn, n.name AS network
LIMIT 10
Sample output:
[
{"host": "cloudflare.com", "ip": "104.16.132.229", "prefix": "104.16.128.0/20", "asn": "AS13335", "network": "CLOUDFLARENET - Cloudflare, Inc."},
{"host": "cloudflare.com", "ip": "104.16.133.229", "prefix": "104.16.128.0/20", "asn": "AS13335", "network": "CLOUDFLARENET - Cloudflare, Inc."}
]
Tip: When writing up an incident, include all rows — a domain may resolve to IPs on different ASNs, which can indicate load balancing, multi-CDN setups, or in rarer cases, BGP hijack artifacts.
Batch IOC Enrichment
You have a list of IPs from an alert and want to enrich them all in a single query.
-- Enrich multiple IPs in one request
UNWIND ["185.220.101.1", "104.16.132.229", "8.8.8.8"] AS ip_addr
MATCH (ip:IPV4 {name: ip_addr})
-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)
-[:ROUTES]->(a:ASN)
RETURN ip_addr, ap.name AS prefix, a.name AS asn
Sample output:
[
{"ip_addr": "185.220.101.1", "prefix": "185.220.101.0/24", "asn": "AS60729"},
{"ip_addr": "104.16.132.229", "prefix": "104.16.128.0/20", "asn": "AS13335"},
{"ip_addr": "8.8.8.8", "prefix": "8.8.8.0/24", "asn": "AS15169"}
]
Tip:
UNWINDprocesses each value as a separate row, so you can pass up to hundreds of indicators in one query. For scored threat assessments, runCALL explain("indicator")separately per IP — inline procedure chaining is not currently supported.
For Threat Intelligence Analysts
You're building a picture of threat actor infrastructure, mapping campaigns, and correlating indicators across sources.
Quick Triage
Expand from One Domain to Its Full Campaign
You have one suspicious domain and want to find other domains hosted on the same infrastructure.
-- Pivot on shared IP: find all domains co-hosted with a suspicious domain
MATCH (h1:HOSTNAME {name: "paypal--confirm.com"})
-[:RESOLVES_TO]->(ip:IPV4)
<-[:RESOLVES_TO]-(h2:HOSTNAME)
WHERE h1 <> h2
RETURN h2.name AS related_domain, ip.name AS shared_ip LIMIT 20
Tip: If the result is empty, the domain may have been taken down (no current A record). Use
CALL whisper.history("domain")to check if it had IP records in the past.
Pivot on Shared WHOIS Contact Email
A threat actor reused the same contact email across multiple registrations — find them all.
-- All domains registered with the same WHOIS email
MATCH (h1:HOSTNAME {name: "cloudflare.com"})
-[:HAS_EMAIL]->(e:EMAIL)
<-[:HAS_EMAIL]-(h2:HOSTNAME)
WHERE h1 <> h2
RETURN h2.name AS related_domain, e.name AS shared_email LIMIT 20
Sample output:
[
{"related_domain": "amp8ball.com", "shared_email": "domains@cloudflare.com"},
{"related_domain": "as13335.com", "shared_email": "domains@cloudflare.com"},
{"related_domain": "asap-cloudflare.com", "shared_email": "domains@cloudflare.com"}
]
Tip: Registrar privacy services often replace real emails with proxy addresses. Check whether the shared email is from a privacy service before drawing attribution conclusions.
Deep Dive Investigation
Pivot on Shared Registrar
When a specific threat actor consistently uses the same registrar, find other domains in that cohort.
-- All domains registered through the same registrar as the seed domain
MATCH (h1:HOSTNAME {name: "cloudflare.com"})
-[:HAS_REGISTRAR]->(r:REGISTRAR)
<-[:HAS_REGISTRAR]-(h2:HOSTNAME)
WHERE h1 <> h2
RETURN h2.name AS related_domain, r.name AS registrar LIMIT 10
Sample output:
[
{"related_domain": "3-m.ac", "registrar": "iana:1910"},
{"related_domain": "180.academy", "registrar": "iana:1910"},
{"related_domain": "1111systems.academy", "registrar": "iana:1910"}
]
Tip: High-volume registrars like MarkMonitor or CSC are used by thousands of legitimate enterprises. More interesting are smaller or obscure registrars that appear in multiple suspicious registrations.
Nameserver Clustering
Find all domains that delegate DNS to the same nameserver — a classic indicator of shared threat actor infrastructure.
-- All domains using a specific nameserver
MATCH (ns:HOSTNAME {name: "ns1.google.com"})
-[:NAMESERVER_FOR]->(h:HOSTNAME)
RETURN h.name LIMIT 20
Sample output:
[
{"h.name": "forum.unicloud.ai"},
{"h.name": "blogspot.al"},
{"h.name": "google.al"}
]
Tip: For investigation, look for private or unusual nameservers rather than major providers. Domains sharing a custom nameserver operated by the attacker are a strong clustering signal.
ASN Threat Profiling
Score the network an IP or domain belongs to.
-- Threat reputation of an entire ASN
CALL explain("AS60729")
Sample output:
[{
"indicator": "AS60729",
"type": "asn",
"found": true,
"score": 0.0,
"level": "NONE",
"explanation": "AS60729 (TORSERVERS-NET, DE) has a reputation score of 51.0 (Suspicious)."
}]
Tip: An ASN with a high threat density score means a large fraction of its IP space is listed in threat feeds. Even if your specific indicator isn't listed, hosting on a high-density ASN is a meaningful risk factor.
Investigate Domain History
Pull historical WHOIS snapshots to see how a domain's registration changed over time.
-- Historical WHOIS snapshots for a domain
CALL whisper.history("cloudflare.com")
Sample output (first 3 of 27 snapshots):
[
{"indicator": "cloudflare.com", "type": "domain", "queryTime": "2024-06-13 18:31:16", "createDate": "2009-02-17", "updateDate": "2024-01-09", "expiryDate": "2033-02-17", "registrar": "CloudFlare, Inc.", "nameServers": "..."},
{"indicator": "cloudflare.com", "type": "domain", "queryTime": "2020-05-01 07:03:56", "createDate": "2009-02-17", "registrar": "Cloudflare, Inc."},
{"indicator": "cloudflare.com", "type": "domain", "queryTime": "2017-06-07 20:16:00", "createDate": "2009-02-17", "registrar": "CloudFlare, Inc."}
]
Tip: Sudden changes in registrar, nameserver, or registrant contact are the most meaningful signals in domain history. A domain that changed registrar and nameserver in the same week is worth investigating.
Evidence Collection
Web Link Analysis
Trace hyperlinks between domains to find sites that reference or redirect to a suspicious target.
-- Sites that link to this domain
MATCH (source:HOSTNAME)-[:LINKS_TO]->(h:HOSTNAME {name: "cloudflare.com"})
RETURN source.name LIMIT 15
Sample output:
[
{"source.name": "0x1.academy"},
{"source.name": "12345.ae"},
{"source.name": "151.ae"}
]
Tip: Outbound links (
h -[:LINKS_TO]-> target) show what sites this domain references. Inbound links (this query) show what sites reference it — useful for finding phishing pages that link to legitimate brand pages to appear credible.
Compare Multiple Indicators at Once
Check registration details for a batch of suspicious domains in one request.
-- Batch WHOIS lookup for multiple domains
UNWIND ["google.com", "cloudflare.com", "microsoft.com"] AS domain
MATCH (h:HOSTNAME {name: domain})
OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR)
RETURN domain, h.name, collect(DISTINCT r.name) AS registrars
Sample output:
[
{"domain": "cloudflare.com", "h.name": "cloudflare.com", "registrars": ["iana:1910"]},
{"domain": "google.com", "h.name": "google.com", "registrars": ["iana:292"]},
{"domain": "microsoft.com", "h.name": "microsoft.com", "registrars": ["iana:292"]}
]
Tip: Domains that don't match in the
MATCHclause won't appear in results. If a domain from your input list is missing from results, it's not in the graph — treat it as an unknown rather than clean.
For Penetration Testers and Red Teams
You're conducting passive reconnaissance on a target. All data here comes from public DNS, BGP, and WHOIS records — no active probing.
Quick Triage
Subdomain Enumeration
Find all indexed subdomains for a target domain.
-- All indexed subdomains of a target
MATCH (h:HOSTNAME) WHERE h.name ENDS WITH ".github.com"
RETURN h.name LIMIT 30
Sample output:
[
{"h.name": "0.github.com"},
{"h.name": "api.github.com"},
{"h.name": "gist.github.com"},
{"h.name": "education.github.com"}
]
Tip: The
ENDS WITH ".target.com"pattern (note the leading dot) returns only subdomains, not the domain itself or domains that merely contain the string. For broad coverage, also run aCHILD_OFquery (see below) which uses the domain hierarchy index.
Count Subdomains Before Enumerating
Before pulling a full subdomain list, check how many exist.
-- How many subdomains are indexed for this target?
MATCH (sub:HOSTNAME)-[:CHILD_OF]->(h:HOSTNAME {name: "github.com"})
RETURN count(sub) AS subdomain_count
Sample output:
[{"subdomain_count": 36131}]
Tip: If subdomain count is in the tens of thousands, the domain is likely a major CDN or hosting provider. Focus on specific interesting prefixes using
STARTS WITHrather than pulling the full list.
Prefix-Based Discovery
Find hosts matching a naming pattern across all domains.
-- All indexed hosts starting with a specific prefix
MATCH (h:HOSTNAME) WHERE h.name STARTS WITH "vpn."
RETURN h.name LIMIT 15
Sample output:
[
{"h.name": "vpn.0--0--0.com"},
{"h.name": "vpn.0--0.uk"},
{"h.name": "vpn.acme-corp.example"}
]
Tip: Combine with domain filtering:
WHERE h.name STARTS WITH "vpn." AND h.name ENDS WITH ".example.com"to target a specific organization's VPN hosts.
Deep Dive Investigation
DNS Hierarchy Walk
Follow the domain tree to understand the full scope of a target's namespace.
-- Full ancestor chain: subdomain -> parent -> TLD
MATCH (h:HOSTNAME {name: "mail.google.com"})-[:CHILD_OF*1..3]->(parent)
RETURN h.name, parent.name LIMIT 10
Sample output:
[
{"h.name": "mail.google.com", "parent.name": "google.com"},
{"h.name": "mail.google.com", "parent.name": "com"}
]
Tip: Use variable-length
[:CHILD_OF*1..3]to walk the hierarchy.*1..3covers up to three levels — enough for most real domain structures.
IP Range Discovery
Collect all IP addresses a target domain resolves to, across all its subdomains.
-- All IPs for all direct children of a target domain
MATCH (sub:HOSTNAME)-[:CHILD_OF]->(h:HOSTNAME {name: "github.com"})
WITH sub LIMIT 5000
MATCH (sub)-[:RESOLVES_TO]->(ip:IPV4)
RETURN DISTINCT ip.name LIMIT 30
Tip: This gives you the IP footprint for a target's first-level subdomains. The
LIMIT 5000on subdomains keeps the query fast while covering enough of the namespace — subdomains are iterated in index order, and many early entries may lack DNS records. Increase the subdomain limit if results are sparse.
Mail Server Discovery
Find the mail servers that handle email for a target domain.
-- MX records: mail servers for a target domain
MATCH (mx:HOSTNAME)-[:MAIL_FOR]->(h:HOSTNAME {name: "cloudflare.com"})
RETURN mx.name
Sample output:
[
{"mx.name": "mxa.global.inbound.cf-emailsecurity.net"},
{"mx.name": "mxa-canary.global.inbound.cf-emailsecurity.net"},
{"mx.name": "mxb.global.inbound.cf-emailsecurity.net"},
{"mx.name": "mxb-canary.global.inbound.cf-emailsecurity.net"}
]
Tip: Mail servers often run on different infrastructure than web servers. This gives you additional IP ranges to scope for the target's attack surface.
CNAME Chain Following
Follow canonical name redirects to find where a subdomain ultimately resolves.
-- Follow CNAME chain to canonical target
MATCH (h:HOSTNAME {name: "www.github.com"})-[:ALIAS_OF*1..5]->(target:HOSTNAME)
RETURN h.name, target.name
Sample output:
[{"h.name": "www.github.com", "target.name": "github.com"}]
Tip: Deep CNAME chains (3+ hops) sometimes reveal cloud provider or CDN vendor relationships that aren't obvious from the surface domain.
SPF Record Mapping
Understand which IP ranges and services are authorized to send email on behalf of a target domain.
-- Full SPF authorization chain for a target domain
MATCH (h:HOSTNAME {name: "google.com"})-[:SPF_INCLUDE]->(spf:HOSTNAME)
OPTIONAL MATCH (spf)-[:SPF_IP]->(range)
RETURN spf.name, collect(range.name) AS authorized_ranges LIMIT 10
Sample output:
[{
"spf.name": "_spf.google.com",
"authorized_ranges": ["74.125.0.0/16", "209.85.128.0/17", "2001:4860:4000::/36", "2404:6800:4000::/36", "2607:f8b0:4000::/36", "2800:3f0:4000::/36", "2a00:1450:4000::/36", "2c0f:fb50:4000::/36"]
}]
Tip: The authorized IP ranges from SPF tell you which cloud providers the target trusts to send email. This is useful for understanding their email vendor relationships.
ASN Prefix Mapping
Enumerate all BGP prefixes announced by a target's ASN.
-- All IP prefixes announced by an ASN
MATCH (a:ASN {name: "AS15169"})-[:ROUTES]->(p)
RETURN a.name, p.name LIMIT 20
Sample output:
[
{"a.name": "AS15169", "p.name": "8.14.69.0/24"},
{"a.name": "AS15169", "p.name": "8.14.84.0/22"},
{"a.name": "AS15169", "p.name": "34.64.68.0/22"}
]
Tip: Count prefixes first with
count(p)before pulling the full list — large ASNs announce thousands of prefixes.
For Brand Protection and Anti-Phishing Teams
You're watching for abuse of your organization's brand in domain registrations, lookalike infrastructure, and phishing kits.
Quick Triage
Brand Name Search
Find all domains in the graph that contain your brand name.
-- All domains containing the brand name
MATCH (h:HOSTNAME) WHERE h.name CONTAINS "paypal"
RETURN h.name LIMIT 20
Sample output:
[
{"h.name": "mail-paypal-security.com"},
{"h.name": "mexico-paypal-fundtransfer.com"},
{"h.name": "paypal--accountsummary.com"},
{"h.name": "paypal--auth.com"},
{"h.name": "paypal--confirm.com"}
]
Tip:
CONTAINSsearches across the entire hostname string — it will match bothpaypal.phishing.exampleandphishing-paypal.example. Combine withLIMITand review results manually before automating alerts.
Lookalike Prefix Scan
Target domains that start with your brand name followed by common abuse patterns.
-- Lookalike domains starting with brand-
MATCH (h:HOSTNAME) WHERE h.name STARTS WITH "paypal-"
RETURN h.name LIMIT 15
Sample output:
[
{"h.name": "paypal--accountsummary.com"},
{"h.name": "paypal--auth.com"},
{"h.name": "paypal--confirm.com"},
{"h.name": "paypal-comp.lang.modula2.com"}
]
Tip: Run multiple variants:
STARTS WITH "yourbrnd-",STARTS WITH "yourbrnd.",CONTAINS "yourbrnd-secure",CONTAINS "yourbrnd-verify". Each pattern catches different typosquatting styles.
Deep Dive Investigation
Phishing Cluster Mapping
A suspicious domain was reported. Find all other domains on the same IP — they may be part of the same phishing kit deployment.
-- All domains sharing an IP with a suspicious domain
MATCH (h1:HOSTNAME {name: "paypal--confirm.com"})
-[:RESOLVES_TO]->(ip:IPV4)
<-[:RESOLVES_TO]-(h2:HOSTNAME)
WHERE h1 <> h2
RETURN h2.name AS related_domain, ip.name AS shared_ip LIMIT 20
Tip: Cross-check the related domains against your brand watchlist — a single phishing kit deployment often targets multiple brands.
WHOIS Registrant Analysis
Check whether a suspicious domain was registered by a known bad actor.
-- Registration details for a suspicious domain
MATCH (h:HOSTNAME {name: "paypal--confirm.com"})
OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR)
OPTIONAL MATCH (h)-[:HAS_EMAIL]->(e:EMAIL)
OPTIONAL MATCH (h)-[:REGISTERED_BY]->(org:ORGANIZATION)
RETURN h.name, collect(DISTINCT r.name) AS registrar,
collect(DISTINCT e.name) AS emails,
collect(DISTINCT org.name) AS org
Tip: If the WHOIS email appears in other suspicious domain registrations, use the shared-email pivot query to map the full cluster.
Shared Nameserver Detection
Find all domains that use the same nameservers as a known abusive domain.
-- All domains using the same nameservers as the seed domain
MATCH (ns:HOSTNAME)-[:NAMESERVER_FOR]->(seed:HOSTNAME {name: "paypal--confirm.com"})
WITH ns
MATCH (ns)-[:NAMESERVER_FOR]->(other:HOSTNAME)
WHERE other.name <> "paypal--confirm.com"
RETURN other.name AS related_domain, ns.name AS nameserver LIMIT 20
Tip: Phishing operators often reuse the same DNS provider or self-operated nameserver across their campaigns. A nameserver cluster is one of the most durable infrastructure signals.
Threat Feed Status
Check whether a suspicious domain is already tracked by threat intelligence.
-- Is this domain listed in any threat feed?
MATCH (h:HOSTNAME {name: "paypal--confirm.com"})-[:LISTED_IN]->(f:FEED_SOURCE)
RETURN h.name, f.name
Tip: An empty result means the domain isn't yet in any feed — not that it's clean. Brand-new phishing domains are often ahead of feed coverage. Combine feed checks with
CALL explain()for a broader signal.
Check Who Links to Brand Domains
Find sites that reference your brand's official domain — useful for identifying parked domains or phishing pages that embed official logos by linking back.
-- External sites that link to your domain
MATCH (source:HOSTNAME)-[:LINKS_TO]->(h:HOSTNAME {name: "cloudflare.com"})
RETURN source.name LIMIT 15
Sample output:
[
{"source.name": "0x1.academy"},
{"source.name": "12345.ae"},
{"source.name": "151.ae"}
]
Tip: Legitimate backlinking is common and noisy. Look for newly registered domains or domains with threat feed hits in the
sourcelist.
For DNS and Email Security Engineers
You're auditing DNS configurations, validating SPF coverage, and checking DNSSEC deployment.
Quick Triage
Nameserver Inventory
Get all nameservers currently delegated for a domain.
-- Authoritative nameservers for a domain
MATCH (ns:HOSTNAME)-[:NAMESERVER_FOR]->(h:HOSTNAME {name: "microsoft.com"})
RETURN ns.name LIMIT 10
Sample output:
[
{"ns.name": "ns1-39.azure-dns.com"},
{"ns.name": "ns2-39.azure-dns.net"},
{"ns.name": "ns3-39.azure-dns.org"},
{"ns.name": "ns4-39.azure-dns.info"}
]
Tip: Multiple NS records across different TLDs (
.com,.net,.org) is standard practice for fault tolerance. If all NS records are under the same TLD, that's a single point of failure.
Mail Server Inventory
List all mail servers that handle inbound email for a domain.
-- MX records for a domain
MATCH (mx:HOSTNAME)-[:MAIL_FOR]->(h:HOSTNAME {name: "google.com"})
RETURN mx.name
Sample output:
[{"mx.name": "smtp.google.com"}]
Tip: The graph stores MX records as
MAIL_FORedges from the mail server hostname to the domain it serves. Cross-check the mail server's own IP and ASN to verify it's on expected infrastructure.
Deep Dive Investigation
SPF Include Chain Audit
Trace the full tree of SPF includes to understand the complete authorized sender set.
-- Recursive SPF include chain (up to 3 hops)
MATCH (h:HOSTNAME {name: "microsoft.com"})-[:SPF_INCLUDE*1..3]->(inc:HOSTNAME)
RETURN DISTINCT inc.name ORDER BY inc.name LIMIT 20
Sample output:
[
{"inc.name": "_spf-a.microsoft.com"},
{"inc.name": "_spf-b.microsoft.com"},
{"inc.name": "_spf-c.microsoft.com"},
{"inc.name": "_spf-d.microsoft.com"},
{"inc.name": "_spf-ssg-a.microsoft.com"}
]
Tip: The SPF spec caps includes at 10 DNS lookups. Deep include chains close to that limit risk "permerror" for legitimate email. Count the distinct nodes returned to check depth.
SPF IP Authorization
See the exact IP ranges authorized to send mail on behalf of a domain.
-- IP ranges authorized in SPF (direct + one level of includes)
MATCH (h:HOSTNAME {name: "google.com"})-[:SPF_INCLUDE]->(spf:HOSTNAME)
OPTIONAL MATCH (spf)-[:SPF_IP]->(range)
RETURN spf.name, collect(range.name) AS authorized_ranges
Sample output:
[{
"spf.name": "_spf.google.com",
"authorized_ranges": ["74.125.0.0/16", "209.85.128.0/17", "2001:4860:4000::/36", "2404:6800:4000::/36"]
}]
Tip: Broad IP ranges (like
/16or/17) in SPF records mean any server in that range can send as your domain. For a security audit, verify each range actually belongs to the email provider you're using.
Full SPF Mechanism Audit
Get a complete breakdown of all SPF mechanisms for a domain.
-- All SPF mechanisms (include, ip, a, mx, redirect, exists)
MATCH (h:HOSTNAME {name: "microsoft.com"})
-[r:SPF_INCLUDE|SPF_IP|SPF_A|SPF_MX|SPF_REDIRECT|SPF_EXISTS]->(target)
RETURN type(r) AS mechanism, target.name AS authorized LIMIT 20
Sample output:
[
{"mechanism": "SPF_INCLUDE", "authorized": "_spf-a.microsoft.com"},
{"mechanism": "SPF_INCLUDE", "authorized": "_spf-b.microsoft.com"},
{"mechanism": "SPF_INCLUDE", "authorized": "_spf-c.microsoft.com"},
{"mechanism": "SPF_INCLUDE", "authorized": "_spf-d.microsoft.com"},
{"mechanism": "SPF_INCLUDE", "authorized": "_spf-ssg-a.microsoft.com"}
]
Tip: Check for
SPF_REDIRECT— a redirect mechanism replaces the entire SPF policy with another domain's policy. If that target domain has a permissive policy, your effective policy is permissive too.
Domain Hierarchy
Understand the parent-child structure of a domain namespace.
-- Walk the domain hierarchy from a subdomain to its roots
MATCH (h:HOSTNAME {name: "mail.google.com"})-[:CHILD_OF*1..3]->(parent)
RETURN h.name AS subdomain, parent.name AS ancestor LIMIT 10
Sample output:
[
{"subdomain": "mail.google.com", "ancestor": "google.com"},
{"subdomain": "mail.google.com", "ancestor": "com"}
]
Tip: Use this to confirm zone delegation — a subdomain should share the same effective zone as its parent unless it has its own NS records.
Batch Nameserver Audit
Check the nameservers for multiple domains at once.
-- Nameservers for a set of domains
UNWIND ["google.com", "cloudflare.com", "microsoft.com"] AS domain
MATCH (h:HOSTNAME {name: domain})
OPTIONAL MATCH (ns:HOSTNAME)-[:NAMESERVER_FOR]->(h)
RETURN domain, collect(ns.name) AS nameservers
Sample output:
[
{"domain": "google.com", "nameservers": ["ns1.google.com", "ns2.google.com", "ns3.google.com", "ns4.google.com"]},
{"domain": "cloudflare.com", "nameservers": ["bella.ns.cloudflare.com", "chelsea.ns.cloudflare.com", "graham.ns.cloudflare.com"]},
{"domain": "microsoft.com", "nameservers": ["ns1-39.azure-dns.com", "ns2-39.azure-dns.net", "ns3-39.azure-dns.org", "ns4-39.azure-dns.info"]}
]
Tip: Comparing nameservers across an organization's portfolio is a fast way to find outliers — a domain that's still on an old DNS provider after a migration, or a test domain left on a default registrar nameserver.
Who Operates a TLD?
You need to identify the registry operator responsible for a top-level domain — useful when escalating abuse complaints or understanding jurisdiction.
-- Registry operator for a TLD
MATCH (op:TLD_OPERATOR {name: "VeriSign Global Registry Services"})-[:OPERATES]->(tld:TLD)
RETURN op.name AS operator, collect(tld.name) AS tlds
Sample output:
[{"operator": "VeriSign Global Registry Services", "tlds": ["com", "net"]}]
Tip: Most TLD operators run only one or a few TLDs. For abuse escalation, the OPERATES relationship gives you the authoritative registry responsible for the zone.
Check Email Security Posture
Quickly assess whether a domain has SPF configured and DNSSEC enabled.
-- Email security posture: SPF and DNSSEC
MATCH (h:HOSTNAME {name: "stripe.com"})
OPTIONAL MATCH (h)-[:SPF_INCLUDE]->(spf)
OPTIONAL MATCH (h)-[:SIGNED_WITH]->(algo:DNSSEC_ALGORITHM)
RETURN h.name,
count(DISTINCT spf) AS spf_includes,
collect(DISTINCT algo.name) AS dnssec_algorithms
Sample output:
[{"h.name": "stripe.com", "spf_includes": 3, "dnssec_algorithms": []}]
Tip: An empty
dnssec_algorithmslist means DNSSEC signing data was not available for this domain.spf_includesgreater than zero confirms an SPF policy exists. For full SPF coverage detail, use the SPF include chain recipe above.
For Network and BGP Security Engineers
You're analyzing routing tables, peering relationships, and IP space allocation.
Quick Triage
ASN Profile
Get the name, prefix count, and peer count for an ASN.
-- ASN identity and scale
MATCH (a:ASN {name: "AS13335"})-[:HAS_NAME]->(n:ASN_NAME)
RETURN a.name AS asn, n.name AS network_name
Sample output:
[{"asn": "AS13335", "network_name": "CLOUDFLARENET - Cloudflare, Inc."}]
-- Count prefixes and peers
MATCH (a:ASN {name: "AS13335"})
OPTIONAL MATCH (a)-[:ROUTES]->(p)
WITH a, count(p) AS prefix_count
OPTIONAL MATCH (a)-[:PEERS_WITH]->(peer:ASN)
RETURN a.name AS asn, prefix_count, count(peer) AS peer_count
Sample output:
[{"asn": "AS13335", "prefix_count": 5556, "peer_count": 1304}]
Tip: The
WITHbetween the twoOPTIONAL MATCHclauses is important — it aggregates prefixes before expanding peers, preventing a Cartesian product. Without it, this query takes seconds instead of milliseconds on large ASNs. For the full reputation profile including threat density, useCALL explain("AS13335").
BGP Peer Analysis
List the ASNs that peer directly with a given network.
-- Direct BGP peers of an ASN
MATCH (a:ASN {name: "AS13335"})-[:PEERS_WITH]->(peer:ASN)
RETURN peer.name LIMIT 20
Sample output:
[
{"peer.name": "AS31"},
{"peer.name": "AS49"},
{"peer.name": "AS112"},
{"peer.name": "AS1764"}
]
Tip: PEERS_WITH represents BGP session data — mutual peering may show as edges in both directions, or only one, depending on how the session was observed.
Count BGP Peers
Check the peering degree of a network before pulling the full list.
-- How many BGP peers does this ASN have?
MATCH (a:ASN {name: "AS3356"})-[:PEERS_WITH]->(peer:ASN)
RETURN count(peer) AS peer_count
Sample output:
[{"peer_count": 6525}]
Tip: Tier-1 carriers like AS3356 (Lumen/CenturyLink) have thousands of peers. For large ASNs, use
count()first, then filter or paginate withLIMITandSKIP.
Deep Dive Investigation
ASN Prefix Inventory
List all IP prefixes announced by an ASN.
-- All prefixes announced by an ASN
MATCH (a:ASN {name: "AS13335"})-[:ROUTES]->(p)
RETURN a.name AS asn, p.name AS prefix LIMIT 20
Sample output:
[
{"asn": "AS13335", "prefix": "1.0.0.0/24"},
{"asn": "AS13335", "prefix": "1.1.1.0/24"},
{"asn": "AS13335", "prefix": "5.11.60.0/23"}
]
Tip: Prefix counts vary significantly. A
/24is the most common announced unit. Seeing/32announcements (single-host routes) is unusual and may indicate traffic engineering or RTBH (Remote Triggered Blackhole) filtering.
IP to Registered Allocation Block
Find the RIR-allocated prefix that contains a given IP.
-- Registered allocation block for an IP
MATCH (ip:IPV4 {name: "1.1.1.1"})-[:BELONGS_TO]->(rp:REGISTERED_PREFIX)
RETURN ip.name AS ip, rp.name AS allocation_block
Sample output:
[{"ip": "1.1.1.1", "allocation_block": "1.1.1.0/24"}]
Tip:
BELONGS_TOreturns the registered allocation block (as assigned by ARIN, RIPE, APNIC, LACNIC, or AFRINIC), not the BGP announcement. These are often different sizes — a single allocation may be announced as multiple more specific prefixes.
Allocation Country
Get the country associated with an IP's registered allocation block.
-- Country of the registered allocation for an IP
MATCH (ip:IPV4 {name: "1.1.1.1"})
-[:BELONGS_TO]->(rp:REGISTERED_PREFIX)
-[:HAS_COUNTRY]->(co:COUNTRY)
RETURN ip.name AS ip, rp.name AS allocation, co.name AS country
Sample output:
[{"ip": "1.1.1.1", "allocation": "1.1.1.0/24", "country": "AU"}]
Tip: This reflects where the block was registered, not where traffic is actually served from. For anycast deployments, the registered country is the network operator's home jurisdiction.
ASN Reputation Assessment
Get a full threat posture for an ASN, including threat density and composite score.
-- Full threat reputation for an ASN
CALL explain("AS60729")
Sample output:
[{
"indicator": "AS60729",
"type": "asn",
"found": true,
"score": 0.0,
"level": "NONE",
"explanation": "AS60729 (TORSERVERS-NET, DE) has a reputation score of 51.0 (Suspicious).",
"breakdown": {"threatDensity": 90, "historicalScore": 60, "composite": 72.0}
}]
Tip: The composite threat score aggregates threat density (fraction of IPs in feeds), historical listings, and other signals. An ASN with low composite but high
threatDensityis actively hosting threats even if it was recently clean.
BGP Routing History
Pull historical routing data for a prefix to see which ASNs have announced it over time — useful for investigating BGP hijacks or tracking IP space transfers.
-- Historical BGP routing records for a prefix
CALL whisper.history("1.1.1.0/24")
Sample output (first 3 of many routing snapshots):
[
{"indicator": "1.1.1.0/24", "type": "routing", "origin": "AS13335", "prefix": "1.1.1.0/24", "startTime": "2018-07-01T00:00:00", "endTime": "2018-07-31T23:59:59", "peersSeing": 312},
{"indicator": "1.1.1.0/24", "type": "routing", "origin": "AS226", "prefix": "1.1.1.0/24", "startTime": "2016-02-05T00:00:00", "endTime": "2016-02-16T23:59:59", "peersSeing": 40},
{"indicator": "1.1.1.0/24", "type": "routing", "origin": "AS237", "prefix": "1.0.0.0/8", "startTime": "2010-02-12T00:00:00", "endTime": "2010-02-23T23:59:59", "peersSeing": 74}
]
Tip: Multiple distinct
originASNs for the same prefix over time can indicate legitimate IP space transfers or historical BGP hijacks. Cross-reference each origin'speersSeingcount — a sudden announcement from a new ASN with high peer visibility is the signature of a hijack.
List All Regional Internet Registries
-- All five RIRs
MATCH (rir:RIR) RETURN rir.name ORDER BY rir.name
Sample output:
[
{"rir.name": "AFRINIC"},
{"rir.name": "APNIC"},
{"rir.name": "ARIN"},
{"rir.name": "LACNIC"},
{"rir.name": "RIPENCC"}
]
For Compliance and Risk Assessment Teams
You're verifying security posture, checking jurisdictional exposure, and building audit evidence.
Quick Triage
Domain Country Exposure Check
Determine what countries a domain's infrastructure is registered in.
-- Country of the IP allocation for a domain's IPs
MATCH (h:HOSTNAME {name: "cloudflare.com"})
-[:RESOLVES_TO]->(ip:IPV4)
-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)
-[:HAS_COUNTRY]->(co:COUNTRY)
RETURN DISTINCT co.name AS country, count(ip) AS ip_count
Sample output:
[{"country": "US", "ip_count": 2}]
Tip: For compliance reviews requiring data residency, use this to check whether a vendor's infrastructure is in permitted jurisdictions. Remember this reflects network registration, not physical server location.
Registrar Verification
Confirm that a domain is registered with an approved registrar.
-- Current registrar for a domain
MATCH (h:HOSTNAME {name: "stripe.com"})-[:HAS_REGISTRAR]->(r:REGISTRAR)
RETURN h.name, r.name
Sample output:
[{"h.name": "stripe.com", "r.name": "iana:447"}]
Tip: Registrar identifiers follow the
iana:NNNNformat where the number is the IANA registrar ID. You can look up any ID at the IANA registrar database (https://www.iana.org/assignments/registrar-ids/).
Registrant Organization Verification
Verify the organization that registered a domain — useful for third-party due diligence and confirming vendor identity.
-- Registrant organization(s) for a domain
MATCH (h:HOSTNAME {name: "stripe.com"})-[:REGISTERED_BY]->(org:ORGANIZATION)
RETURN h.name, org.name LIMIT 5
Sample output:
[
{"h.name": "stripe.com", "org.name": "domain admin"},
{"h.name": "stripe.com", "org.name": "stripe"}
]
Tip: WHOIS records often contain multiple organization entries — the registrant, administrative contact, and technical contact may each have a distinct organization field. Use
collect(DISTINCT org.name)to deduplicate.
Deep Dive Investigation
Full Security Profile
Build a comprehensive registration and DNS profile for compliance documentation.
-- Complete security profile: registrar, org, nameservers, mail servers, SPF
MATCH (h:HOSTNAME {name: "microsoft.com"})
OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR)
OPTIONAL MATCH (h)-[:REGISTERED_BY]->(org:ORGANIZATION)
OPTIONAL MATCH (ns:HOSTNAME)-[:NAMESERVER_FOR]->(h)
OPTIONAL MATCH (mx:HOSTNAME)-[:MAIL_FOR]->(h)
RETURN h.name,
collect(DISTINCT r.name) AS registrar,
collect(DISTINCT org.name) AS org,
collect(DISTINCT ns.name) AS nameservers,
collect(DISTINCT mx.name) AS mailservers
Sample output:
[{
"h.name": "microsoft.com",
"registrar": ["iana:292"],
"org": ["domain administrator", "microsoft corporation"],
"nameservers": ["ns1-39.azure-dns.com", "ns2-39.azure-dns.net", "ns3-39.azure-dns.org", "ns4-39.azure-dns.info"],
"mailservers": ["microsoft-com.mail.protection.outlook.com"]
}]
Tip: Use
OPTIONAL MATCHthroughout — mandatoryMATCHfor any WHOIS field will return no results for domains where that field is absent. EachOPTIONAL MATCHindependently returns null if no match exists.
Batch Domain Audit
Run a security check across a portfolio of domains.
-- Batch audit: registrar and nameservers for multiple domains
UNWIND ["google.com", "cloudflare.com", "stripe.com"] AS domain
MATCH (h:HOSTNAME {name: domain})
OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR)
OPTIONAL MATCH (ns:HOSTNAME)-[:NAMESERVER_FOR]->(h)
RETURN domain,
collect(DISTINCT r.name) AS registrar,
collect(DISTINCT ns.name) AS nameservers
Tip: For large domain portfolios, break the
UNWINDlist into batches of 50-100 domains per query to stay within response size limits.
WHOIS History for Compliance Evidence
Pull a timestamped audit trail of registration changes.
-- Historical WHOIS record for evidence collection
CALL whisper.history("google.com")
Sample output (first 2 of 36 snapshots):
[
{"indicator": "google.com", "type": "domain", "queryTime": "2024-11-05 21:15:21", "createDate": "1997-09-05", "updateDate": "2024-08-02", "expiryDate": "2028-09-13", "registrar": "MarkMonitor, Inc.", "country": "US"},
{"indicator": "google.com", "type": "domain", "queryTime": "2016-06-14 05:41:13", "createDate": "1997-09-15", "updateDate": "2015-06-12", "expiryDate": "2020-09-14", "registrar": "MarkMonitor, Inc."}
]
Tip: Historical WHOIS is available for many popular domains. The
queryTimefield shows when each snapshot was captured. Gaps in history are normal — WHOIS scraping doesn't capture every change.
For Cyber Insurance and Third-Party Risk
You're conducting non-intrusive external assessments of an organization's internet-facing infrastructure for underwriting or vendor risk scoring.
Quick Triage
External Posture Snapshot
Build a rapid profile of a company's external DNS and email infrastructure.
-- Infrastructure overview: registrar, nameservers, mail servers, SPF
MATCH (h:HOSTNAME {name: "stripe.com"})
OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR)
OPTIONAL MATCH (ns:HOSTNAME)-[:NAMESERVER_FOR]->(h)
OPTIONAL MATCH (mx:HOSTNAME)-[:MAIL_FOR]->(h)
OPTIONAL MATCH (h)-[:SPF_INCLUDE]->(spf:HOSTNAME)
RETURN h.name,
collect(DISTINCT r.name) AS registrar,
collect(DISTINCT ns.name) AS nameservers,
collect(DISTINCT mx.name) AS mailservers,
count(DISTINCT spf) AS spf_includes
Sample output:
[{
"h.name": "stripe.com",
"registrar": ["iana:447"],
"nameservers": ["ns-423.awsdns-52.com", "ns-705.awsdns-24.net"],
"mailservers": ["aspmx.l.google.com"],
"spf_includes": 1
}]
Tip: SPF include count above 0 indicates the organization has published an email authorization policy. The presence of recognized cloud DNS providers (AWS Route53, Azure DNS, Google Cloud DNS) in nameservers is a positive hygiene signal.
Threat Exposure Check
Check whether any of a company's IPs are currently listed in threat feeds.
-- Threat feed hits for a domain's IPs
MATCH (h:HOSTNAME {name: "cloudflare.com"})
-[:RESOLVES_TO]->(ip:IPV4)
OPTIONAL MATCH (ip)-[:LISTED_IN]->(f:FEED_SOURCE)
RETURN ip.name, collect(f.name) AS threat_feeds
Tip: Major cloud providers like Cloudflare and AWS host many customers — the fact that an IP is on their network says little about the specific customer. Focus on whether the specific hostname resolves to a threat-listed IP, not whether the CDN's overall address space has listings.
Hosting Provider Identification
Identify who is hosting a company's primary web infrastructure.
-- Hosting provider: domain -> IP -> BGP prefix -> ASN -> name
MATCH (h:HOSTNAME {name: "cloudflare.com"})
-[:RESOLVES_TO]->(ip:IPV4)
-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)
-[:ROUTES]->(a:ASN)
-[:HAS_NAME]->(n:ASN_NAME)
RETURN DISTINCT a.name AS asn, n.name AS provider LIMIT 5
Sample output:
[{"asn": "AS13335", "provider": "CLOUDFLARENET - Cloudflare, Inc."}]
Tip: Enterprise organizations typically host across 2-5 ASNs (primary CDN, cloud provider, legacy data center). Multiple ASNs with distinct providers indicates better infrastructure resilience.
Domain Threat Score
Get a standardized threat score for underwriting use.
-- Composite threat assessment for a domain
CALL explain("cloudflare.com")
Sample output:
[{
"indicator": "cloudflare.com",
"type": "domain",
"found": true,
"score": 4.05,
"level": "INFO",
"explanation": "cloudflare.com is listed in 2 threat feed(s). Score 4.1 (Informational - minimal risk)."
}]
Tip: The
levelfield gives a normalized tier:NONE,INFO,LOW,MEDIUM,HIGH,CRITICAL. For automated underwriting rules, uselevelrather than the rawscore— the tier boundaries are calibrated for human-readable risk language.
For Law Enforcement and Cybercrime Investigators
You're building a documented infrastructure map for criminal investigation. Every query here uses public data only.
Quick Triage
IP Attribution Chain
Trace a suspicious IP to its responsible network operator for legal process.
-- Full attribution: IP -> prefix -> ASN -> operator name
MATCH (ip:IPV4 {name: "185.220.101.1"})
-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)
-[:ROUTES]->(a:ASN)
RETURN ip.name AS ip, ap.name AS prefix, a.name AS asn
Sample output:
[{"ip": "185.220.101.1", "prefix": "185.220.101.0/24", "asn": "AS60729"}]
Tip: The ASN identifies the network operator responsible for the IP block. Use
CALL explain("ASXXXXX")to get the operator's name and country, which you'll need to identify the correct legal process jurisdiction.
Full Ownership Chain
Document all publicly available registration data for a suspicious domain.
-- Complete ownership record for a domain
MATCH (h:HOSTNAME {name: "cloudflare.com"})
OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR)
OPTIONAL MATCH (h)-[:HAS_EMAIL]->(e:EMAIL)
OPTIONAL MATCH (h)-[:HAS_PHONE]->(p:PHONE)
OPTIONAL MATCH (h)-[:REGISTERED_BY]->(org:ORGANIZATION)
OPTIONAL MATCH (h)-[:PREV_REGISTRAR]->(pr:REGISTRAR)
RETURN h.name,
collect(DISTINCT r.name) AS current_registrar,
collect(DISTINCT pr.name) AS previous_registrars,
collect(DISTINCT e.name) AS emails,
collect(DISTINCT p.name) AS phones,
collect(DISTINCT org.name) AS organizations
Sample output:
[{
"h.name": "cloudflare.com",
"current_registrar": ["iana:1910"],
"previous_registrars": ["registrar:network solutions, llc.", "iana:1910", "iana:2"],
"emails": ["domains@cloudflare.com", "noreply@data-protected.net"],
"phones": ["+10000000000", "+16503198930"],
"organizations": ["cloudflare hostmaster", "cloudflare,"]
}]
Tip:
PREV_REGISTRARgives the historical registrar chain — useful for documenting when a domain changed hands. Phone numbers are in E.164 format when available.
Deep Dive Investigation
Related Domain Discovery via Shared Contact
Pivot from a known suspicious domain to related infrastructure through shared registration attributes.
-- All domains registered with the same contact email
MATCH (h1:HOSTNAME {name: "cloudflare.com"})
-[:HAS_EMAIL]->(e:EMAIL)
<-[:HAS_EMAIL]-(h2:HOSTNAME)
WHERE h1 <> h2
RETURN h2.name AS related_domain, e.name AS contact_email LIMIT 20
Tip: Document the pivot chain clearly: "Domain A shares email address X with Domain B, as confirmed by WHOIS data captured [date]."
Web Link Evidence
Document hyperlinks between domains for demonstrating operational connections.
-- Pages this domain links to
MATCH (h:HOSTNAME {name: "github.com"})-[:LINKS_TO]->(target:HOSTNAME)
RETURN target.name LIMIT 20
Sample output:
[
{"target.name": "34c3ctf.ccc.ac"},
{"target.name": "10xse.academy"},
{"target.name": "01.ai"}
]
Tip: Web link data is crawl-based and reflects the web graph at the time of last indexing. Include the timestamp from
CALL whisper.history()when citing graph data in legal documents.
Historical Registration Evidence
Pull timestamped WHOIS history for evidentiary documentation.
-- Historical WHOIS snapshots with timestamps
CALL whisper.history("google.com")
Tip: Each row in the result includes a
queryTimefield showing when that WHOIS snapshot was captured. When citing this data as evidence, include the full snapshot timestamp and the source attribution.
For Security Researchers and Academics
You're studying internet topology, deployment trends, and ecosystem characteristics at scale.
Schema Exploration
Explore the Graph Schema
Understand what node types and relationship types exist in the graph.
-- All node labels with counts
CALL db.labels()
Sample output (first 5 of 18):
[
{"label": "HOSTNAME", "count": 2631997144},
{"label": "IPV4", "count": 618914961},
{"label": "EMAIL", "count": 237065663},
{"label": "ORGANIZATION", "count": 119189847},
{"label": "PHONE", "count": 60194142}
]
-- All edge types with counts
CALL db.relationshipTypes()
Sample output (first 5 of 20):
[
{"type": "LINKS_TO", "count": 10851011448},
{"type": "NAMESERVER_FOR", "count": 8881831888},
{"type": "RESOLVES_TO", "count": 2919321504},
{"type": "CHILD_OF", "count": 2338085185},
{"type": "REGISTERED_BY", "count": 916255242}
]
Tip: These procedures return O(1) histogram lookups — they're instantaneous even on billion-scale data. Use them to plan query strategies before diving into traversals.
Threat Feed Catalog
List all 40 threat intelligence feeds indexed in the graph.
-- Complete threat feed catalog
MATCH (f:FEED_SOURCE) RETURN f.name ORDER BY f.name
Sample output (first 10 of 40):
[
{"f.name": "AlienVault Reputation"},
{"f.name": "Binary Defense Banlist"},
{"f.name": "Blocklist.de All"},
{"f.name": "Blocklist.de Mail"},
{"f.name": "Blocklist.de SSH"},
{"f.name": "Botvrij Domains"},
{"f.name": "Botvrij Dst IPs"},
{"f.name": "Brute Force Blocker"},
{"f.name": "C2 Intel 30d"},
{"f.name": "C2 Tracker"}
]
The full feed list also includes: Cert.pl Domains, CINS Score, Cloudflare Radar Top 1M, DNS RD Abuse, Dan Tor Exit, ET Compromised IPs, Feodo Tracker, FireHOL Abusers 1d, FireHOL Anonymous, FireHOL Level 1-3, FireHOL WebClient, GreenSnow Blacklist, Hagezi Light, Hagezi Pro, IPsum, InterServer RBL, MalwareBazaar Recent, OpenPhish Feed, SSH Client Attacks, SSH Password Auth, SSL IP Blacklist, Spamhaus DROP, Spamhaus EDROP, StevenBlack Hosts, ThreatFox IOCs, Tor Exit Nodes, Tranco Top 1M, URLhaus Recent.
Threat Category Taxonomy
Explore the 18-category threat taxonomy used across feeds.
-- All threat categories
MATCH (c:CATEGORY) RETURN c.name ORDER BY c.name
Sample output:
[
{"c.name": "Ad/Tracking Blocklists"},
{"c.name": "Anonymization Infrastructure"},
{"c.name": "Attack Sources"},
{"c.name": "Brute Force"},
{"c.name": "C2 Servers"},
{"c.name": "General Blacklists"},
{"c.name": "Malicious Domains"},
{"c.name": "Malicious Infrastructure"},
{"c.name": "Malware Distribution"},
{"c.name": "Phishing"},
{"c.name": "Popularity/Trust"},
{"c.name": "Proxies"},
{"c.name": "Reference Data"},
{"c.name": "Reputation"},
{"c.name": "Spam"},
{"c.name": "TOR Network"},
{"c.name": "Threat Intelligence"},
{"c.name": "VPNs"}
]
Research Queries
DNSSEC Algorithm Reference
List all DNSSEC signing algorithm types indexed in the graph schema.
-- All DNSSEC signing algorithm types in the schema
MATCH (algo:DNSSEC_ALGORITHM) RETURN collect(algo.name) AS algorithms
Sample output:
[{"algorithms": ["ECDSAP256SHA256", "ECDSAP384SHA384", "ED25519", "ED448", "RSASHA1", "RSASHA1-NSEC3-SHA1", "RSASHA256", "RSASHA512"]}]
Tip: The graph recognizes 8 DNSSEC algorithm types. To check whether a specific domain has DNSSEC signing data, query
OPTIONAL MATCH (h:HOSTNAME {name: "example.com"})-[:SIGNED_WITH]->(algo:DNSSEC_ALGORITHM) RETURN collect(algo.name). An empty list indicates no DNSSEC data is currently available for that domain.
ASN Peering Degree Analysis
Study the degree distribution of ASN peering relationships.
-- Peering degree for a set of well-known networks
UNWIND ["AS13335", "AS3356", "AS15169"] AS asn_name
MATCH (a:ASN {name: asn_name})-[:PEERS_WITH]->(peer:ASN)
RETURN asn_name, count(peer) AS peer_count
Sample output:
[
{"asn_name": "AS13335", "peer_count": 1304},
{"asn_name": "AS3356", "peer_count": 6525},
{"asn_name": "AS15169", "peer_count": 137}
]
Tip: AS3356 (Lumen) is a Tier-1 carrier and has far more peers than AS15169 (Google), which is primarily a content network. This illustrates the structural difference between transit and content ASNs.
Web Graph Analysis: Outbound Link Degree
Measure the number of outbound hyperlinks from a known domain.
-- Outbound link count from a domain
MATCH (h:HOSTNAME {name: "github.com"})-[:LINKS_TO]->(target:HOSTNAME)
RETURN count(target) AS outbound_links
Tip: The
LINKS_TOgraph contains over 10 billion edges, making it one of the largest datasets in the graph. Queries without an anchored starting node will time out.
Shortest Path Between Two Domains
Find the minimum number of hops between two domains in the graph.
-- Shortest path between two domains
MATCH p = shortestPath(
(h:HOSTNAME {name: "cloudflare.com"})-[*1..6]-(target:HOSTNAME {name: "google.com"})
)
RETURN [n IN nodes(p) | n.name] AS path
Sample output:
[{"path": ["cloudflare.com", "google.com"]}]
Tip: These two domains link directly to each other. For less directly connected domains, paths of 3-5 hops are typical through the web link graph.
Cross-Cutting Recipes
Patterns that work across multiple use cases and personas.
CNAME Chain Traversal
Follow an arbitrary CNAME chain to find the canonical hostname.
-- Follow CNAME aliases to the canonical target
MATCH (h:HOSTNAME {name: "www.github.com"})-[:ALIAS_OF*1..5]->(canonical:HOSTNAME)
RETURN h.name AS alias, canonical.name AS canonical_host
Sample output:
[{"alias": "www.github.com", "canonical_host": "github.com"}]
Registered Allocation for an IP
-- Registered allocation block and organization for an IP
MATCH (ip:IPV4 {name: "1.1.1.1"})
-[:BELONGS_TO]->(rp:REGISTERED_PREFIX)
OPTIONAL MATCH (rp)-[:REGISTERED_BY]->(org:ORGANIZATION)
OPTIONAL MATCH (rp)-[:HAS_COUNTRY]->(co:COUNTRY)
RETURN ip.name AS ip, rp.name AS allocation,
org.name AS registered_org, co.name AS country
Sample output:
[{"ip": "1.1.1.1", "allocation": "1.1.1.0/24", "registered_org": "APNIC Research and Development", "country": "AU"}]
Tip: The organization name under
REGISTERED_BYcomes from RIR WHOIS records. Some entries are organization handles; others are full names depending on how the registry published the data.
Threat Assessment for a CIDR Range
Get network-level threat density for a subnet.
-- Threat assessment for a CIDR range
CALL explain("185.220.101.0/24")
Sample output:
[{
"indicator": "185.220.101.0/24",
"type": "network",
"found": true,
"score": 0.0,
"level": "MEDIUM",
"explanation": "Network 185.220.101.0/24 contains 167 listed IPs across multiple threat feeds."
}]
Find Domains Linking Out to a Suspicious Site
-- Who links to a suspicious domain?
MATCH (source:HOSTNAME)-[:LINKS_TO]->(h:HOSTNAME {name: "cloudflare.com"})
RETURN source.name LIMIT 15
Quick Reference
Endpoint
POST https://graph.whisper.security/api/query
Content-Type: application/json
X-API-Key: <your-key> (optional)
{"query": "CYPHER QUERY HERE"}
Node Labels
| Label | What It Represents |
|---|---|
HOSTNAME | Any DNS name: domains, subdomains, mail servers |
IPV4 | IPv4 addresses |
IPV6 | IPv6 addresses |
ASN | Autonomous System (format: AS15169) |
ASN_NAME | Human-readable ASN name |
PREFIX | Physical BGP prefix |
REGISTERED_PREFIX | RIR-allocated block (virtual) |
ANNOUNCED_PREFIX | BGP-announced prefix at query time (virtual) |
TLD | Top-level domain label |
REGISTRAR | Domain registrar (format: iana:292) |
EMAIL | WHOIS contact email address |
PHONE | WHOIS contact phone (E.164 format) |
ORGANIZATION | WHOIS registrant or RIR org |
CITY | GeoIP city (format: "City, CC") |
COUNTRY | ISO 3166-1 alpha-2 country code |
RIR | Regional Internet Registry |
FEED_SOURCE | Threat intelligence feed (40 feeds) |
CATEGORY | Threat category (18 categories) |
DNSSEC_ALGORITHM | DNSSEC signing algorithm |
Edge Types
| Edge | Traversal Pattern | Notes |
|---|---|---|
RESOLVES_TO | HOSTNAME → IPV4/IPV6 | DNS A/AAAA |
ANNOUNCED_BY | IPV4 → ANNOUNCED_PREFIX | BGP routing (virtual) |
ROUTES | ANNOUNCED_PREFIX → ASN | Virtual BGP chain |
BELONGS_TO | IPV4 → REGISTERED_PREFIX | RIR allocation |
HAS_NAME | ASN → ASN_NAME | Network display name |
PEERS_WITH | ASN → ASN | BGP peering |
CHILD_OF | HOSTNAME → HOSTNAME/TLD | Domain hierarchy |
NAMESERVER_FOR | HOSTNAME(NS) → HOSTNAME | DNS delegation |
MAIL_FOR | HOSTNAME(MX) → HOSTNAME | MX record |
ALIAS_OF | HOSTNAME → HOSTNAME | CNAME |
LINKS_TO | HOSTNAME → HOSTNAME | Web hyperlinks |
HAS_REGISTRAR | HOSTNAME → REGISTRAR | Current registrar |
PREV_REGISTRAR | HOSTNAME → REGISTRAR | Historical registrar |
HAS_EMAIL | HOSTNAME → EMAIL | WHOIS contact |
HAS_PHONE | HOSTNAME → PHONE | WHOIS contact |
REGISTERED_BY | HOSTNAME/PREFIX → ORGANIZATION | Registrant org |
HAS_COUNTRY | PREFIX/CITY → COUNTRY | Country code |
LOCATED_IN | IPV4 → CITY | GeoIP |
LISTED_IN | IPV4/HOSTNAME → FEED_SOURCE | Threat intel (virtual) |
SPF_INCLUDE | HOSTNAME → HOSTNAME | SPF include |
SPF_IP | HOSTNAME → IPV4/PREFIX | SPF ip: mechanism |
SPF_A | HOSTNAME → HOSTNAME | SPF a: mechanism |
SPF_MX | HOSTNAME → HOSTNAME | SPF mx: mechanism |
SPF_REDIRECT | HOSTNAME → HOSTNAME | SPF redirect: |
SPF_EXISTS | HOSTNAME → HOSTNAME | SPF exists: |
SIGNED_WITH | HOSTNAME → DNSSEC_ALGORITHM | DNSSEC signing |
Procedures
| Procedure | What It Does |
|---|---|
CALL explain("indicator") | Threat score and explanation (IP, domain, ASN, CIDR) |
CALL whisper.history("indicator") | Historical WHOIS and BGP snapshots |
CALL whisper.quota() | Current plan and usage |
CALL db.labels() | All node labels with counts |
CALL db.relationshipTypes() | All edge types with counts |
Performance Rules
| Do This | Not That |
|---|---|
MATCH (h:HOSTNAME {name: "example.com"}) | MATCH (h:HOSTNAME) WHERE h.name = "example.com" |
WHERE h.name STARTS WITH "mail." | WHERE h.name =~ "^mail\\..*" |
WHERE h.name ENDS WITH ".example.com" | WHERE h.name =~ ".*\\.example\\.com$" |
Always add LIMIT | Open-ended traversals on billion-node labels |
CALL db.relationshipTypes() for counts | MATCH ()-[r]->() RETURN count(r) |
OPTIONAL MATCH for WHOIS fields | Mandatory MATCH for sparse fields |
Query Complexity Guide
| Speed | Pattern |
|---|---|
| Instant | Anchored point lookups {name: "..."} |
| Fast (<1s) | STARTS WITH, ENDS WITH ".domain", CONTAINS |
| Medium (1-5s) | Multi-hop traversals, WHOIS with OPTIONAL MATCH |
| Slow | LINKS_TO traversals, full text scan across all HOSTNAME |
| Avoid | Unanchored label scans, regex =~ on HOSTNAME |