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.

Updated April 2026

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

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 20 as 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_BY resolves BGP routing data at query time, so you always get current routing information. For IPs where BGP data is unavailable, BELONGS_TO gives 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]-> COUNTRY to 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_IN returns 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 MATCH for WHOIS fields — not every domain has every field populated. A plain MATCH would 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: UNWIND processes each value as a separate row, so you can pass up to hundreds of indicators in one query. For scored threat assessments, run CALL 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

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 MATCH clause 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 a CHILD_OF query (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 WITH rather 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..3 covers 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 5000 on 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

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: CONTAINS searches across the entire hostname string — it will match both paypal.phishing.example and phishing-paypal.example. Combine with LIMIT and 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.

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 source list.


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_FOR edges 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 /16 or /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_algorithms list means DNSSEC signing data was not available for this domain. spf_includes greater 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 WITH between the two OPTIONAL MATCH clauses 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, use CALL 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 with LIMIT and SKIP.

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 /24 is the most common announced unit. Seeing /32 announcements (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_TO returns 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 threatDensity is 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 origin ASNs for the same prefix over time can indicate legitimate IP space transfers or historical BGP hijacks. Cross-reference each origin's peersSeing count — 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:NNNN format 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 MATCH throughout — mandatory MATCH for any WHOIS field will return no results for domains where that field is absent. Each OPTIONAL MATCH independently 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 UNWIND list 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 queryTime field 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 level field gives a normalized tier: NONE, INFO, LOW, MEDIUM, HIGH, CRITICAL. For automated underwriting rules, use level rather than the raw score — 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_REGISTRAR gives the historical registrar chain — useful for documenting when a domain changed hands. Phone numbers are in E.164 format when available.

Deep Dive Investigation

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]."

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 queryTime field 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.

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_TO graph 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_BY comes 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

LabelWhat It Represents
HOSTNAMEAny DNS name: domains, subdomains, mail servers
IPV4IPv4 addresses
IPV6IPv6 addresses
ASNAutonomous System (format: AS15169)
ASN_NAMEHuman-readable ASN name
PREFIXPhysical BGP prefix
REGISTERED_PREFIXRIR-allocated block (virtual)
ANNOUNCED_PREFIXBGP-announced prefix at query time (virtual)
TLDTop-level domain label
REGISTRARDomain registrar (format: iana:292)
EMAILWHOIS contact email address
PHONEWHOIS contact phone (E.164 format)
ORGANIZATIONWHOIS registrant or RIR org
CITYGeoIP city (format: "City, CC")
COUNTRYISO 3166-1 alpha-2 country code
RIRRegional Internet Registry
FEED_SOURCEThreat intelligence feed (40 feeds)
CATEGORYThreat category (18 categories)
DNSSEC_ALGORITHMDNSSEC signing algorithm

Edge Types

EdgeTraversal PatternNotes
RESOLVES_TOHOSTNAME → IPV4/IPV6DNS A/AAAA
ANNOUNCED_BYIPV4 → ANNOUNCED_PREFIXBGP routing (virtual)
ROUTESANNOUNCED_PREFIX → ASNVirtual BGP chain
BELONGS_TOIPV4 → REGISTERED_PREFIXRIR allocation
HAS_NAMEASN → ASN_NAMENetwork display name
PEERS_WITHASN → ASNBGP peering
CHILD_OFHOSTNAME → HOSTNAME/TLDDomain hierarchy
NAMESERVER_FORHOSTNAME(NS) → HOSTNAMEDNS delegation
MAIL_FORHOSTNAME(MX) → HOSTNAMEMX record
ALIAS_OFHOSTNAME → HOSTNAMECNAME
LINKS_TOHOSTNAME → HOSTNAMEWeb hyperlinks
HAS_REGISTRARHOSTNAME → REGISTRARCurrent registrar
PREV_REGISTRARHOSTNAME → REGISTRARHistorical registrar
HAS_EMAILHOSTNAME → EMAILWHOIS contact
HAS_PHONEHOSTNAME → PHONEWHOIS contact
REGISTERED_BYHOSTNAME/PREFIX → ORGANIZATIONRegistrant org
HAS_COUNTRYPREFIX/CITY → COUNTRYCountry code
LOCATED_INIPV4 → CITYGeoIP
LISTED_INIPV4/HOSTNAME → FEED_SOURCEThreat intel (virtual)
SPF_INCLUDEHOSTNAME → HOSTNAMESPF include
SPF_IPHOSTNAME → IPV4/PREFIXSPF ip: mechanism
SPF_AHOSTNAME → HOSTNAMESPF a: mechanism
SPF_MXHOSTNAME → HOSTNAMESPF mx: mechanism
SPF_REDIRECTHOSTNAME → HOSTNAMESPF redirect:
SPF_EXISTSHOSTNAME → HOSTNAMESPF exists:
SIGNED_WITHHOSTNAME → DNSSEC_ALGORITHMDNSSEC signing

Procedures

ProcedureWhat 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 ThisNot 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 LIMITOpen-ended traversals on billion-node labels
CALL db.relationshipTypes() for countsMATCH ()-[r]->() RETURN count(r)
OPTIONAL MATCH for WHOIS fieldsMandatory MATCH for sparse fields

Query Complexity Guide

SpeedPattern
InstantAnchored point lookups {name: "..."}
Fast (<1s)STARTS WITH, ENDS WITH ".domain", CONTAINS
Medium (1-5s)Multi-hop traversals, WHOIS with OPTIONAL MATCH
SlowLINKS_TO traversals, full text scan across all HOSTNAME
AvoidUnanchored label scans, regex =~ on HOSTNAME