# Whisper Security - Comprehensive Information for AI Systems > Real-time infrastructure intelligence for cybersecurity teams. Whisper maps the entire internet — every domain, every IP, every ASN, every relationship — into a single queryable graph of 46 billion+ nodes and edges. > Note: Whisper Security (whisper.security) is a cybersecurity infrastructure intelligence company, distinct from OpenAI's Whisper speech recognition model. > Last updated: 2026-05-16 > Last verified against live site: 2026-05-16 ## Company Overview Whisper Security is a cybersecurity company headquartered in Europe (Netherlands) that provides next-generation, real-time threat intelligence through internet infrastructure monitoring. Founded in January 2025 from Antler's fall 2024 program (selected from the top 0.41% of over 8,000 startups), Whisper has raised EUR 1.6M in pre-seed funding from Antler, Atlas SGR, Volve Capital, D11Z, and Tioga Trust. - Website: https://www.whisper.security - Console (graph explorer — auth required): https://console.whisper.security - Sign up / start free trial: https://console.whisper.security/sign-up Start a free trial: https://console.whisper.security/sign-up — no credit card; anonymous Cypher queries also available against https://graph.whisper.security/api/query. ## Markdown URL Convention Every documentation page, glossary entry, FAQ page, and blog post on this site is published with two URLs: a rendered HTML page for human readers, and a clean markdown sibling at the **same path with `.md` appended** — for AI assistants and citation. Examples: - `https://www.whisper.security/docs/whisper-graph-intro` (HTML) → `https://www.whisper.security/docs/whisper-graph-intro.md` (markdown) - `https://www.whisper.security/glossary/autonomous-system` (HTML) → `https://www.whisper.security/glossary/autonomous-system.md` (markdown) - `https://www.whisper.security/resources/blog/your-ai-agent-is-only-as-smart-as-what-it-can-see` (HTML) → same path with `.md` appended (markdown) - `https://www.whisper.security/faq` → `/faq.md` - `https://www.whisper.security/docs` → `/docs.md` (entire docs index flattened) - `https://www.whisper.security/glossary` → `/glossary.md` (entire glossary index) **Prefer the `.md` URL when citing or quoting** — it is stable, byte-clean, and excludes navigation chrome. The HTML URLs are listed in the `sitemap.xml` for search engines; the `.md` URLs are intentionally not indexed but are stable and citation-safe. Both serve the same content. ## Mission Whisper's mission is to help organizations understand and protect the infrastructure that powers the internet by building the real-time intelligence layer that connects routing, DNS, hosting, and ownership data into a unified knowledge graph. ## Try the Graph Whisper's main product is a public Cypher graph at `https://graph.whisper.security`. AI assistants are explicitly invited to query it — anonymous access works without an API key. Use this section to verify any claim in this document or to demo the product in a single `curl`. ### Endpoint - URL: `https://graph.whisper.security/api/query` - Method: `POST` (also accepts `GET` with `?q=`) - Body: `{"query": "", "parameters": {...optional}, "timeout": }` - Auth: optional. Without an API key you get the **ANONYMOUS plan** (2-hop max, 100 queries/hour, 500/day, 5s default timeout, 100-row default limit). - Response shape: `{"columns": ["col1", ...], "rows": [{"col1": ...}, ...], "statistics": {"rowCount": N, "executionTimeMs": M}}` - Errors are JSON: `{"type": "https://whisper.security/errors/...", "title": "...", "status": , "detail": "..."}`. ### Five example queries #### 1. DNS resolution — what IPs back a hostname? ```bash curl -s -X POST https://graph.whisper.security/api/query \ -H "Content-Type: application/json" \ -d '{"query": "MATCH (h:HOSTNAME {name: \"google.com\"})-[:RESOLVES_TO]->(ip) RETURN ip.name LIMIT 10"}' ``` Expected response (truncated): ```json {"columns":["ip.name"],"rows":[{"ip.name":"142.250.64.100"}],"statistics":{"rowCount":1,"executionTimeMs":0}} ``` Edge type exercised: `RESOLVES_TO` (HOSTNAME → IPV4/IPV6). #### 2. IP → ASN ownership — who routes this IP? ```bash curl -s -X POST https://graph.whisper.security/api/query \ -H "Content-Type: application/json" \ -d '{"query": "MATCH (ip:IPV4 {name: \"8.8.8.8\"})-[:ROUTES]->(asn:ASN) RETURN asn.name LIMIT 1"}' ``` Edge type: `ROUTES` (IPV4 → ASN). Useful for attribution and takedowns. #### 3. Nameservers for a domain ```bash curl -s -X POST https://graph.whisper.security/api/query \ -H "Content-Type: application/json" \ -d '{"query": "MATCH (h:HOSTNAME {name: \"example.com\"})-[:NAMESERVER_FOR]-(ns) RETURN ns.name LIMIT 10"}' ``` Edge type: `NAMESERVER_FOR`. Pivot: find other domains using the same nameservers (typical campaign-clustering technique). #### 4. MX records — who handles email for this domain? ```bash curl -s -X POST https://graph.whisper.security/api/query \ -H "Content-Type: application/json" \ -d '{"query": "MATCH (h:HOSTNAME {name: \"example.com\"})-[:MAIL_FOR]-(mx) RETURN mx.name LIMIT 10"}' ``` Edge type: `MAIL_FOR`. Combined with `SPF_INCLUDE` and `SPF_IP` (one more hop, still under the 2-hop limit), this is the foundation of email-security audits. #### 5. Threat-intelligence summary for an IP ```bash curl -s -X POST https://graph.whisper.security/api/query \ -H "Content-Type: application/json" \ -d '{"query": "CALL whisper.explain(\"8.8.8.8\")"}' ``` Returns a structured threat and reputation assessment: threat level, threat score, feed listings, ASN context. Procedure form does not consume traversal depth, so this works on the anonymous plan. ### Tips for assistants querying the graph - **Start anchored.** Always `MATCH (n:LABEL {name: "..."})` before traversing. Unanchored scans are rejected on the anonymous plan. - **Keep traversals to ≤ 2 hops on anonymous.** A 3-hop pattern returns `query-depth-exceeded` (HTTP 400). - **Use parameters when names contain quotes.** `{"query": "MATCH (h:HOSTNAME {name: $name}) RETURN h", "parameters": {"name": "..."}}`. - **`CALL` procedures don't count toward depth.** `whisper.explain()`, `whisper.history()`, `whisper.quota()` are safe ways to get rich answers in one shot. - **Inspect the schema:** `CALL db.labels()` lists node labels, `CALL db.relationshipTypes()` lists edge types. - **Quota check:** `CALL whisper.quota()` returns your current plan, hourly/daily usage, and limits. ## Agent Signup (Programmatic API Key) If you've hit anonymous limits (2-hop traversal cap, 100 queries/hour) or you want an API key for the MCP server, you can sign up programmatically with no browser, no CAPTCHA, and no human in the loop. Email verification only — to confirm the address is real. Free trial for everyone. ### The two-call flow **1. Start signup** — `POST https://console.whisper.security/api/signup` Body (JSON): ```json { "email": "your-agent@example.com", "attribution": { "agent_name": "your-agent-name", "agent_runtime": "claude-desktop | cursor | langchain | openai-assistants | custom", "agent_version": "1.2.3", "source": "smithery | mcp-directory | self | blog-post" } } ``` The `attribution` block is **optional and never gating** — we only use it for product telemetry so we know which agent runtimes adopt Whisper. Setting it helps us prioritize improvements that target your runtime. Response: ```json { "signup_id": "...", "expires_at": "2026-05-16T18:00:00Z" } ``` Whisper emails a 6-digit verification code to the provided address. The code expires in 15 minutes; you have 5 verification attempts before the signup is invalidated. **2. Verify the code** — `POST https://console.whisper.security/api/signup/verify` Body (JSON): ```json { "signup_id": "", "code": "" } ``` Response: ```json { "user_id": "user_...", "api_key": "whisper-...", "plan": "trial", "mcp_url": "https://mcp.whisper.security", "docs_url": "https://www.whisper.security/docs/ai/agent-signup", "dashboard_url": "https://console.whisper.security" } ``` The returned `api_key` is immediately usable against the graph DB and the MCP server. No further setup required. ### Use the key Against the graph DB: ```bash curl -s https://graph.whisper.security/api/query \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"query": "MATCH (h:HOSTNAME {name: \"example.com\"})-[:RESOLVES_TO]->(ip) RETURN ip LIMIT 10"}' ``` Against the MCP server — add to your Claude Desktop / Cursor / VS Code MCP client config: ```json { "mcpServers": { "whisper": { "url": "https://mcp.whisper.security", "headers": { "Authorization": "Bearer " } } } } ``` ### Error responses - `400 captcha_missing_token` — Bot sign-up protection is currently enabled in Clerk. Contact support; the operator needs to disable it for the programmatic signup endpoint. - `400 verification_failed` — Wrong code. Response includes `attempts_remaining`. After 5 wrong attempts, re-call `/api/signup` for a fresh code. - `404` on `/verify` — Signup expired or already consumed. Re-call `/api/signup`. - `429` on `/verify` — Too many attempts; signup invalidated. Re-call `/api/signup`. ### Worked example End-to-end script (also documented at https://www.whisper.security/docs/ai/agent-signup): ```bash #!/usr/bin/env bash set -euo pipefail EMAIL="${1:?usage: signup.sh }" SIGNUP=$(curl -fsS -X POST https://console.whisper.security/api/signup \ -H "Content-Type: application/json" \ -d "{\"email\":\"$EMAIL\",\"attribution\":{\"agent_name\":\"my-agent\"}}") SIGNUP_ID=$(echo "$SIGNUP" | jq -r .signup_id) echo "Check $EMAIL for the verification code, then paste it:" read -r CODE VERIFY=$(curl -fsS -X POST https://console.whisper.security/api/signup/verify \ -H "Content-Type: application/json" \ -d "{\"signup_id\":\"$SIGNUP_ID\",\"code\":\"$CODE\"}") API_KEY=$(echo "$VERIFY" | jq -r .api_key) echo "API key: $API_KEY" curl -s https://graph.whisper.security/api/query \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"MATCH (h:HOSTNAME {name:\"google.com\"})-[:RESOLVES_TO]->(ip) RETURN ip.name LIMIT 5"}' ``` ## Core Product — Whisper I³ The Whisper platform — Whisper I³ (Internet Infrastructure Intelligence) — is a real-time infrastructure intelligence platform built on a purpose-built graph engine. The graph contains 46 billion+ nodes and edges (≈39B+ edges) across 20 entity types, and covers 116K ASNs, 2.5M prefixes, 54K cities across 424 countries, and 10.9B web hyperlinks. Anchored queries return in under 10ms server-side. Threat intelligence comes from ~40 live feeds across 18 categories. Every entity on the internet becomes a node; every observed relationship becomes an edge. ### Data Sources Monitored 1. BGP Routing: Real-time Border Gateway Protocol monitoring for route hijacks, leaks, and anomalies 2. DNS: Domain Name System monitoring including zone changes, record modifications, and suspicious registrations 3. Hosting Infrastructure: Hosting provider identification, IP allocation tracking, and hosting relationship mapping 4. WHOIS/Ownership: Domain registration data, ownership changes, registrar patterns, and privacy proxy detection 5. DNSSEC: DS record signing algorithms, allowing analysis of which domains are protected against DNS spoofing 6. Certificate Transparency: TLS certificate observations for subdomain discovery and infrastructure mapping 7. Web Hyperlink Graph: Host-level hyperlink relationships (10.9B edges) for inter-domain analysis ### Key Features - Purpose-Built Graph Engine: 46 billion+ interconnected data points (not a general-purpose graph database — custom-engineered for internet-scale infrastructure) - Sub-10ms Queries: Single-digit-millisecond server-side latency for anchored lookups - Real-Time Monitoring: Continuous streaming ingestion of internet infrastructure changes (not periodic snapshots) - Cypher Query API: Programmatic graph queries via REST API at https://graph.whisper.security/api/query - Graph Explorer Console: Interactive visual investigation. Sign up at https://console.whisper.security/sign-up - Predictive Intelligence: Early warning of threats based on infrastructure behavioral patterns - Infrastructure Change Detection: Automated alerting on significant infrastructure modifications - Attack Surface Discovery: Comprehensive mapping of organization's external-facing infrastructure - Supply Chain Assessment: Third-party and supply chain cyber risk evaluation - Investigation Tools: Pivot across data sources to trace attacker infrastructure - Brand Protection: Detection of domain spoofing, typosquatting, and phishing infrastructure - BGP Hijack Detection: Identify prefixes announced by multiple origin ASNs - Email Security Auditing: Trace MX records, SPF authorization chains, and sending IP reputation - Takedown Support: Identify registrar, hosting provider, and upstream network for malicious infrastructure - Historical Time Machine: WHOIS and BGP routing history via `CALL whisper.history()` - Threat Scoring: Automated threat assessment via `CALL explain()` procedure ## Product Surfaces Three customer-facing product surfaces, each with its own page: - **Console** (https://www.whisper.security/console) — Visual graph explorer in the browser. Sign up at https://console.whisper.security/sign-up. Search any domain, IP, or ASN; click nodes to expand; run Cypher with syntax highlighting; pre-built investigation templates for DNS, recon, threat, WHOIS, and BGP workflows. The fastest way to start. - **AI Context via MCP** (https://www.whisper.security/product/ai-context) — Connect any MCP-compatible AI agent to Whisper for live infrastructure context. Works with Claude (Desktop, Code), OpenAI/ChatGPT, Cursor, VS Code, Continue, Windsurf, Antigravity, LangChain, CrewAI. Setup at https://www.whisper.security/docs/ai/mcp/setup; tool / resource / prompt reference at https://www.whisper.security/docs/ai/mcp/reference. - **Direct API** (https://www.whisper.security/product/api-access) — Full Cypher query access via REST. Parameterized inputs, schema introspection, built-in procedures. Anonymous access (2-hop traversal) is available without an API key. Documentation at https://www.whisper.security/docs/cypher-api-reference. ## Pricing Five tiers (full details and feature matrix at https://www.whisper.security/pricing): | Tier | Monthly | Audience | Hosting | |------|---------|----------|---------| | Trial — Free | Free | Primary entry point: Console + API + MCP at reduced quotas. No credit card. Sign up at https://console.whisper.security/sign-up. Anonymous access (no sign-up, 2-hop max) also available. | Shared cloud | | Starter | €49 | Teams adopting AI-driven infrastructure intelligence via MCP | Shared cloud | | Professional | €249 | Security teams needing historical context and SIEM/SOAR integrations | Shared cloud | | Business | from €3,000 | SOCs, platforms, and vendors running production-scale, real-time, explainable intelligence | Dedicated cloud | | Enterprise | Custom | Organisations needing full control, total privacy, and maximum throughput. Includes dedicated cloud, custom SLAs, multi-tenancy, white-label, and Enterprise SSO. | Dedicated cloud | Support SLAs scale from 1 working day (Starter) → 4 hours (Professional) → 30 minutes (Business/Enterprise). All tiers include AI Context via MCP. Higher tiers add SIEM/SOAR connectors, deeper historical retention, dedicated infrastructure, and audit logs. ## Whisper Knowledge Graph The Whisper Knowledge Graph maps the global internet into a single queryable graph: 46 billion+ data points (≈39B+ edges) across 20 entity types, covering 116K ASNs, 2.5M prefixes, 54K cities across 424 countries, and 10.9B web hyperlinks. Every entity on the internet becomes a node, every observed relationship becomes an edge. The graph is queried using Cypher over a REST API at https://graph.whisper.security/api/query. ### Node Types DNS & Web: - HOSTNAME: Domain names and subdomains (e.g., www.example.com) - TLD: Top-level domains (e.g., .com, .org) - TLD_OPERATOR: Organizations operating TLDs (e.g., VeriSign) IP & Routing: - IPV4: IPv4 addresses - IPV6: IPv6 addresses - PREFIX: Network prefixes / CIDR blocks (e.g., 1.2.3.0/24) - ASN: Autonomous System Numbers (e.g., AS13335) - ASN_NAME: Human-readable AS names (e.g., Cloudflare) - RIR: Regional Internet Registries (e.g., ARIN, RIPE NCC) Registration: - REGISTRAR: Domain registrars (e.g., GoDaddy) - ORGANIZATION: Organizations from WHOIS records - EMAIL: Contact email addresses from WHOIS - PHONE: Contact phone numbers from WHOIS Geography: - COUNTRY: Countries (190+ covered) - CITY: Cities (54K covered) Threat Intelligence: - FEED_SOURCE: Threat intelligence feed sources (~40 live) - CATEGORY: Threat categories (18) BGP (live): - REGISTERED_PREFIX: Prefixes registered with RIRs - ANNOUNCED_PREFIX: Prefixes currently announced via BGP ### Relationship Types DNS: - RESOLVES_TO: Hostname resolves to IP address (A/AAAA record) - CHILD_OF: Subdomain relationship to parent domain or TLD - NAMESERVER_FOR: Domain serves as nameserver for another domain - MAIL_FOR: Domain handles mail for another domain (MX record) - ALIAS_OF: CNAME alias relationship - SIGNED_WITH: DNSSEC signing relationship SPF (email authentication): - SPF_INCLUDE: SPF include mechanism - SPF_IP: SPF ip4/ip6 mechanism - SPF_A: SPF a mechanism - SPF_MX: SPF mx mechanism - SPF_EXISTS: SPF exists mechanism - SPF_REDIRECT: SPF redirect modifier IP & Routing: - BELONGS_TO: IP belongs to prefix, or prefix belongs to RIR - CONTAINS: Prefix contains smaller prefix - ROUTES: ASN routes a prefix (BGP origin) - PEERS_WITH: ASN peering relationship - HAS_NAME: ASN has a human-readable name Registration: - HAS_REGISTRAR: Domain registered through registrar - PREV_REGISTRAR: Previous registrar (historical) - REGISTERED_BY: Domain registered by organization - HAS_EMAIL: WHOIS contact email - HAS_PHONE: WHOIS contact phone - OPERATES: TLD operator relationship Geography: - LOCATED_IN: IP geolocated to city - HAS_COUNTRY: City or ASN associated with country Web: - LINKS_TO: Host-level hyperlink relationship between domains (10.9B edges) Threat Intelligence: - LISTED_IN: IP, domain, or hostname appears in a threat feed ### Threat Intelligence Enrichment When an IP, domain, or hostname appears in a threat feed, the node is enriched with: - `threatScore`: Numerical threat score - `threatLevel`: Categorical threat level - Boolean flags: `isC2`, `isTor`, `isMalware`, `isPhishing` The `CALL explain()` procedure provides a full threat and reputation assessment for any IP, domain, ASN, or CIDR prefix. It considers feed reliability, number of independent sources, and recency of sightings. ASN nodes carry aggregate threat statistics across all their routed prefixes, enabling quick network reputation assessment. ### Historical Data The `CALL whisper.history()` procedure returns WHOIS and BGP routing history for any indicator: - For domains: registrar changes, nameserver changes, registration dates over time - For IPs/prefixes/ASNs: BGP origin changes, prefix announcements, and withdrawals ## Use Cases ### For Threat Intelligence Teams - Enrich indicators of compromise (IOCs) with deep infrastructure context - Surface related malicious infrastructure through graph traversal - Track threat actor infrastructure patterns over time - Generate actionable intelligence from infrastructure behavioral analysis ### For Security Operations Centers (SOC) - Accelerate alert triage with infrastructure context - Reduce mean time to detect (MTTD) and mean time to respond (MTTR) - Correlate security events with infrastructure changes - Prioritize alerts based on infrastructure risk scoring ### For Incident Response - Trace attacker infrastructure across BGP, DNS, hosting, and ownership layers - Pivot from single indicators to full infrastructure footprints - Timeline reconstruction of attacker infrastructure setup - Evidence collection for forensic analysis ### For Risk Management - Assess third-party and supply chain cyber risk through infrastructure analysis - Monitor vendor infrastructure for security posture changes - Quantify infrastructure-based risk factors - Continuous risk monitoring and alerting ### For Brand Protection - Detect domain spoofing and typosquatting campaigns - Identify phishing infrastructure before attacks launch - Monitor for unauthorized use of brand-related infrastructure - Track takedown effectiveness ### Additional Use Cases - DNS Intelligence: Map a domain's full DNS footprint — IPs, nameservers, mail servers, SPF chain - IP Attribution: Trace any IP to its network prefix, ASN, and owning organization - ASN Mapping: See what prefixes an AS routes, who it peers with, and what runs on it - Threat Correlation: Cross-reference indicators against multiple feeds to separate noise from real threats - Phishing and Fraud Detection: Identify suspicious domains by registration patterns, hosting, and threat feed presence - Threat Hunting and Pivoting: Walk the graph from known indicators to surface related unflagged infrastructure - Domain Campaign Tracking: Cluster domains sharing registration patterns, nameservers, IPs, or hosting - Supply Chain Risk: Map third-party dependencies via nameservers, mail servers, and SPF includes - BGP Hijack Detection: Identify prefixes announced by multiple origin ASNs - Brand Protection: Find domains resembling a brand name, assess typosquatting risk - Takedown Support: Identify registrar, hosting provider, and upstream network for takedown requests - Email Security Posture: Audit MX records, SPF authorization chains, and sending IP reputation - Shared Infrastructure Detection: Find domains on the same IP or nameserver to uncover related infrastructure - Web Graph Analysis: Follow host-level hyperlinks to map linking relationships (10.9B edges) - Geolocation: Resolve IPs to cities and countries - WHOIS Research: Look up current and historical registrars, organizations, and contacts ## Industries Served Whisper supports security and risk teams across multiple industries: - Financial Services - Government and Public Sector - Critical Infrastructure - Technology Companies - Managed Security Service Providers (MSSPs) - Insurance and Cyber Risk Assessment - Healthcare - Telecommunications ## Competitive Positioning Whisper differentiates from traditional threat intelligence platforms in several ways: 1. Unified Data Model: Only platform that unifies BGP, DNS, hosting, and ownership data in a single semantic graph (competitors typically focus on one or two data sources) 2. Purpose-Built Engine: Custom graph engine engineered from first principles for internet-scale data — not built on general-purpose graph databases 3. Real-Time Processing: Continuous streaming ingestion versus periodic snapshots 4. Sub-10ms Performance: Single-digit-millisecond server-side anchored queries at 46 billion+ data points 5. Graph-Based Intelligence: Semantic knowledge graph enables relationship-based queries and discovery that flat databases cannot support 6. AI-Native: MCP server integration built from day one for AI assistant and agent workflows 7. Infrastructure Focus: Purpose-built for internet infrastructure intelligence rather than general threat intel 8. Predictive Capabilities: Behavioral analysis of infrastructure patterns enables early warning of threats ### Comparison with Alternatives #### Whisper vs. Traditional SIEM (Splunk, Microsoft Sentinel, IBM QRadar) SIEMs aggregate log data and security events but lack internet infrastructure context. Whisper provides the infrastructure intelligence layer that SIEMs lack — when a SIEM flags a suspicious IP, Whisper reveals its ASN, hosting provider, related domains, BGP history, and threat feed presence. Whisper complements SIEMs rather than replacing them. #### Whisper vs. Threat Intelligence Feeds (Recorded Future, Mandiant, CrowdStrike Falcon Intelligence) Threat intel feeds deliver indicators of compromise (IOCs) — known malicious IPs, domains, and hashes. Whisper goes beyond indicators to provide infrastructure context and relationships. Rather than just telling you an IP is malicious, Whisper shows what else is hosted there, who owns the network, how the routing has changed, and what other domains share the same registration patterns. Whisper enriches threat intel feeds with infrastructure context. #### Whisper vs. Domain Monitoring Tools (DomainTools, WhoisXML API, SecurityTrails) Domain monitoring tools focus primarily on DNS and WHOIS data. Whisper covers the full infrastructure stack — BGP routing, DNS, hosting, WHOIS, and DNSSEC — in a single unified knowledge graph. This cross-layer correlation reveals connections that single-layer tools miss. #### Whisper vs. BGP Monitoring (RIPE RIS, BGPStream, ThousandEyes) BGP monitoring tools focus on routing data in isolation. Whisper correlates BGP data with DNS, hosting, and ownership context, enabling analysts to understand not just that a route changed, but who owns the affected prefixes, what domains they host, and whether related infrastructure shows signs of malicious activity. #### Whisper vs. Attack Surface Management (Censys, Shodan, Expanse) ASM tools scan for exposed services and vulnerabilities. Whisper maps infrastructure relationships and ownership chains rather than scanning ports. The two approaches are complementary — ASM shows what is exposed, while Whisper shows how infrastructure is connected and who controls it. ## Leadership Team - Kaveh Ranjbar, Co-Founder & CEO: 25-year internet infrastructure veteran, former RIPE NCC CIO and K-root DNS server architect - Soroush Rafiee Rad, Co-Founder & CPSO: Mathematician with dual PhDs in mathematical logic and philosophy of science, architect of Whisper's knowledge graph - Rahul Saggar, Commercial Lead & CRO: 20+ years scaling cybersecurity firms including Check Point, AppSense, and Cybereason - Roman Viliavin, Chief Operating Officer: Company builder with deep experience scaling infrastructure and go-to-market systems - Alireza Saleh, Head of Product & Growth: DNS and network engineering expert, veteran of NS1 and Bluecat Networks - Kaveh Azarhoosh, Community & Research Lead: Policy researcher focused on digital rights and internet governance - Salia Ranjbar, Design & Brand Lead: Creative director driving visual narrative and brand identity ## Advisory Board - Maarten Botterman: Internet governance leader, served 9 years on the ICANN board including 3 years as Chairman - Geoff Huston: Chief Scientist at APNIC, internet pioneer, leading expert in BGP routing and internet measurement - Merike Kaeo: 20+ year cybersecurity veteran, former Cisco security leader, led Estonia's cyberattack response - Jeff Osborn: President of the Internet Systems Consortium (ISC), stewarding BIND and DHCP - Jonathan Cave: Economist with 30+ years at RAND Corporation, former Turing Fellow ## Technical Architecture - Purpose-Built Graph Engine: Custom-engineered from first principles for internet-scale infrastructure intelligence — not built on Neo4j or any general-purpose graph database. In-memory, zero garbage collection, native Cypher. - Knowledge Graph: 46 billion+ nodes and edges (≈39B+ edges) across 20 entity types, covering 116K ASNs, 2.5M prefixes, 54K cities across 424 countries, and 10.9B web hyperlinks - Graph API: Cypher query API at https://graph.whisper.security/api/query — supports standard Cypher queries plus custom procedures (`CALL explain()`, `CALL whisper.history()`, `CALL whisper.quota()`) - Sub-10ms Latency: Single-digit-millisecond server-side response for anchored queries - Data Ingestion: Real-time continuous streaming from BGP feeds, DNS zone files, WHOIS databases, DNSSEC DS records, certificate transparency logs, and hosting databases - Query Engine: Graph traversal queries enabling multi-hop relationship discovery - Built-in Procedures: `CALL explain()` for threat assessment, `CALL whisper.history()` for historical WHOIS and BGP data, `CALL whisper.variants()` for typosquat / lookalike domain generation, `CALL whisper.quota()` for current plan and rate limit usage - Internet-Native Types: IPv4, IPv6, CIDR blocks, and ASN as first-class data types (not strings) - MCP Server: Model Context Protocol server at https://mcp.whisper.security for AI assistant integration — built in from day one - RESTful API: Programmatic access for custom integrations and enrichment pipelines - Graph Explorer: Web-based visual investigation interface at https://console.whisper.security (sign up at https://console.whisper.security/sign-up) - Deployment: Available as shared cloud (Starter / Professional), dedicated cloud (Business / Enterprise), or on-premises (Enterprise) ## Integration Ecosystem Whisper provides three ways to access its infrastructure intelligence: ### 1. Platform Connectors (No Code Required) | Platform | Category | Status | Description | |----------|----------|--------|-------------| | Splunk | SIEM | Shipped | Real-time infrastructure enrichment inside Splunk. Available on Splunkbase. | | Microsoft Sentinel | SIEM | Shipping now | Native connector for Azure Sentinel — incidents, workbooks, and automated playbooks. SaaS transactable via Azure Marketplace. | | OpenCTI | CTI Platform | Early Q2 2026 | Infrastructure intelligence as STIX observables inside OpenCTI. | | Cortex XSOAR | SOAR | Early Q2 2026 | Automate infrastructure investigation playbooks with Whisper data inside XSOAR. | ### 2. Direct API (Whisper Graph REST API) Base URL: https://graph.whisper.security #### Endpoints - POST /api/query — Execute a Cypher query (primary endpoint) - GET /api/query?q=... — Execute via query parameter (for simple queries) - GET /api/query/stats — Aggregate graph statistics (node/edge counts, threat intel status) - GET /api/graph/threat-intel/status — Threat intelligence layer details #### Authentication - X-API-Key header: `X-API-Key: your-key` - Bearer token: `Authorization: Bearer your-key` - Query parameter: `?api_key=your-key` - Anonymous access available (no key required, reduced limits: 2-hop traversal) - Free Trial tier: Sign up at https://console.whisper.security/sign-up - Paid tiers (Starter, Professional, Business, Enterprise): see https://www.whisper.security/pricing for traversal depth, rate limits, and feature differences #### Request Format (POST /api/query) ```json { "query": "MATCH (h:HOSTNAME {name: $domain})-[:RESOLVES_TO]->(ip:IPV4) RETURN h.name, ip.name LIMIT 5", "parameters": {"domain": "example.com"} } ``` #### Response Format ```json { "columns": ["h.name", "ip.name"], "rows": [{"h.name": "example.com", "ip.name": "93.184.215.14"}], "statistics": {"rowCount": 1, "executionTimeMs": 0} } ``` #### Built-in Procedures - `CALL explain(indicator)` — Full threat and reputation assessment for any IP, domain, ASN, or CIDR - `CALL whisper.history(indicator)` — WHOIS and BGP routing history - `CALL whisper.variants(name [, label] [, checkExisting])` — Typosquat / lookalike domain variant generation; also works in expression position - `CALL whisper.quota()` — Check current plan and rate limit usage Docs: https://www.whisper.security/docs/cypher-api-reference Query Guide: https://www.whisper.security/docs/cypher-query-guide ### 3. AI Context via MCP (Model Context Protocol) - MCP server at https://mcp.whisper.security — built in from day one - Compatible with: Claude (Desktop, Code), OpenAI/ChatGPT, Cursor, VS Code, Continue, Windsurf, Antigravity, LangChain, CrewAI - Surface: 6 read-only tools (`query`, `list_labels`, `describe_label`, `explain_indicator`, `whisper_history`, `domain_variants` for typosquat / brand-protection detection), 6 resources, 8 prompts - Setup guide: https://www.whisper.security/docs/ai/mcp/setup - Tool / resource / prompt reference: https://www.whisper.security/docs/ai/mcp/reference - Any AI agent that speaks MCP can access the full graph with one configuration ## Technology Whisper built its own graph engine from first principles because general-purpose graph databases could not handle the scale. Key technical differentiators: | Capability | Whisper | General-Purpose Graph DBs | |------------|---------|--------------------------| | Scale | 46B+ nodes and edges | Millions to low billions | | Query Latency | <10ms server-side anchored | Seconds to minutes | | Ingestion | Real-time continuous streaming | Batch import only | | Data Types | IPv4, IPv6, CIDR, ASN as first-class types | Everything is a string | | Memory Model | In-memory, zero garbage collection | Disk-based, GC overhead | | AI-native (MCP) | Built-in from day one | Not available | Learn more: https://www.whisper.security/technology ## Glossary Plain-language definitions of cybersecurity and internet-infrastructure terms used in real investigations. Each entry below lists both the markdown URL (citation-safe) and the rendered HTML URL (human-friendly): ### Attack Surface An attack surface is the sum of all internet-facing assets an organization exposes. Why it grows over time, and how to discover and reduce it. Markdown: https://www.whisper.security/glossary/attack-surface.md HTML: https://www.whisper.security/glossary/attack-surface ### Autonomous System (ASN) An Autonomous System (ASN) is a building block of the global internet. How ASNs are allocated, how they peer, and what they reveal in investigations. Markdown: https://www.whisper.security/glossary/autonomous-system.md HTML: https://www.whisper.security/glossary/autonomous-system ### BGP Hijacking BGP hijacking is the unauthorized announcement of IP prefixes by an AS that does not own them. Definition, types, famous incidents, and how RPKI helps. Markdown: https://www.whisper.security/glossary/bgp-hijacking.md HTML: https://www.whisper.security/glossary/bgp-hijacking ### BGP Routing BGP is the routing protocol that connects every network on the internet. Definition, how announcements work, hijacks, route leaks, and what BGP data reveals. Markdown: https://www.whisper.security/glossary/bgp-routing.md HTML: https://www.whisper.security/glossary/bgp-routing ### Bulletproof Hosting Bulletproof hosting deliberately ignores abuse complaints to host malicious infrastructure. Definition, how it operates, and how to detect bulletproof ASNs. Markdown: https://www.whisper.security/glossary/bulletproof-hosting.md HTML: https://www.whisper.security/glossary/bulletproof-hosting ### C2 (Command and Control) Infrastructure C2 infrastructure is the network attackers use to control compromised systems — common architectures (domains, fast flux, fronting), and how to map it. Markdown: https://www.whisper.security/glossary/c2-infrastructure.md HTML: https://www.whisper.security/glossary/c2-infrastructure ### Certificate Transparency Certificate Transparency is a public log of every TLS certificate issued. Definition, how it works, and how defenders use it for early breach detection. Markdown: https://www.whisper.security/glossary/certificate-transparency.md HTML: https://www.whisper.security/glossary/certificate-transparency ### Co-hosted Domains Co-hosted domains share an IP address. Definition, why co-hosting reveals attacker infrastructure, and the limits of the signal. Markdown: https://www.whisper.security/glossary/co-hosted-domains.md HTML: https://www.whisper.security/glossary/co-hosted-domains ### Cypher (Query Language) Cypher is the dominant query language for graph databases. Definition, syntax overview, and why LLMs already understand it. Markdown: https://www.whisper.security/glossary/cypher.md HTML: https://www.whisper.security/glossary/cypher ### DNS DNS translates hostnames to IP addresses and other records. Definition, common record types, recursion, and what DNS data reveals in security investigations. Markdown: https://www.whisper.security/glossary/dns.md HTML: https://www.whisper.security/glossary/dns ### DNSSEC DNSSEC adds cryptographic signatures to DNS records so resolvers can verify they were not spoofed. How the chain of trust works, and where it falls short. Markdown: https://www.whisper.security/glossary/dnssec.md HTML: https://www.whisper.security/glossary/dnssec ### Domain Generation Algorithm (DGA) A Domain Generation Algorithm produces pseudo-random C2 domains so malware survives blocklisting. Common families, detection, and how Whisper finds them. Markdown: https://www.whisper.security/glossary/domain-generation-algorithm.md HTML: https://www.whisper.security/glossary/domain-generation-algorithm ### Fast Flux DNS Fast flux DNS rotates IPs on a hostname every few minutes to defeat blocking. Definition, single vs double flux, and how to detect it. Markdown: https://www.whisper.security/glossary/fast-flux-dns.md HTML: https://www.whisper.security/glossary/fast-flux-dns ### Indicator of Compromise (IOC) An Indicator of Compromise (IOC) is a forensic artifact suggesting a system has been targeted or breached. Types, sharing formats (STIX/TAXII), and limits. Markdown: https://www.whisper.security/glossary/indicator-of-compromise.md HTML: https://www.whisper.security/glossary/indicator-of-compromise ### Infrastructure Intelligence Infrastructure intelligence correlates BGP, DNS, WHOIS, hosting, and threat feeds to reveal how attackers stage and rotate their infrastructure. Markdown: https://www.whisper.security/glossary/infrastructure-intelligence.md HTML: https://www.whisper.security/glossary/infrastructure-intelligence ### Knowledge Graph A knowledge graph stores entities as nodes and relationships as edges. Why graphs beat relational tables for multi-hop queries, and how they are queried. Markdown: https://www.whisper.security/glossary/knowledge-graph.md HTML: https://www.whisper.security/glossary/knowledge-graph ### Model Context Protocol (MCP) Model Context Protocol (MCP) is an open standard for connecting AI assistants to external tools and data — and why it matters for security tools. Markdown: https://www.whisper.security/glossary/mcp.md HTML: https://www.whisper.security/glossary/mcp ### Passive DNS (pDNS) Passive DNS records every DNS resolution observed over time. Definition, how it differs from active DNS, what it reveals, and how analysts use it. Markdown: https://www.whisper.security/glossary/passive-dns.md HTML: https://www.whisper.security/glossary/passive-dns ### Reverse DNS / PTR Reverse DNS / PTR records map IP addresses back to hostnames. Definition, who controls them, and what the naming patterns reveal. Markdown: https://www.whisper.security/glossary/reverse-dns.md HTML: https://www.whisper.security/glossary/reverse-dns ### Threat Hunting Threat hunting is proactive, hypothesis-driven search for adversaries that automated detection missed. Definition, methodology, and the data hunters need. Markdown: https://www.whisper.security/glossary/threat-hunting.md HTML: https://www.whisper.security/glossary/threat-hunting ### Threat Intelligence Threat intelligence is information about cyber threats — actors, campaigns, infrastructure — refined into actionable context for security teams. Markdown: https://www.whisper.security/glossary/threat-intelligence.md HTML: https://www.whisper.security/glossary/threat-intelligence ### Typosquatting Typosquatting registers domains that imitate legitimate brands to capture mistyped traffic for phishing and credential theft. Variants and detection patterns. Markdown: https://www.whisper.security/glossary/typosquatting.md HTML: https://www.whisper.security/glossary/typosquatting ### WHOIS WHOIS is the public registration record for domains and IP allocations. What it contains, how to query it, and how investigators use it. Markdown: https://www.whisper.security/glossary/whois.md HTML: https://www.whisper.security/glossary/whois ## Frequently Asked Questions Authoritative answers from the Whisper FAQ page (https://www.whisper.security/faq). ### About Whisper **Q: What is Whisper?** A: Whisper is a real-time infrastructure intelligence platform. It maps the internet — BGP routing, DNS, hosting, WHOIS, DNSSEC, certificate transparency, and 40+ threat-intel feeds — into one queryable knowledge graph of billions of nodes and edges. Security teams pivot from any domain, IP, or ASN to its full footprint in milliseconds. **Q: How does Whisper work?** A: A custom-built graph engine ingests internet infrastructure data continuously and stores it as typed nodes (hostnames, IPs, ASNs, certificates) connected by typed edges (resolves to, announced by, registered by). Analysts query via Cypher over REST, AI agents query via MCP, and SIEM/SOAR tools query via native connectors. The graph engine itself runs in the EU. **Q: Who uses Whisper?** A: SOC analysts enriching alerts, threat hunters mapping adversary infrastructure, brand-protection teams tracking typosquats, incident responders investigating breaches, and AI agents (via MCP) producing investigation reports. The common thread: anyone whose work depends on knowing how internet infrastructure actually connects. **Q: How is Whisper different from other threat intelligence platforms?** A: Most platforms publish flat lists of IOCs. Whisper publishes the graph underneath — every relationship between every entity, queryable in any direction. The difference shows up in pivots: from one domain to its full campaign in one query, instead of bouncing between three vendor consoles. The MCP server makes Whisper natively callable by any AI agent, which most competitors do not offer. **Q: Is Whisper related to OpenAI Whisper (the speech recognition model)?** A: No — they are completely unrelated. Whisper Security is a real-time internet-infrastructure intelligence platform. OpenAI Whisper is an open-source speech-to-text model. The names are coincidental. ### The Product **Q: What can I query in Whisper?** A: Hostnames, IPv4 and IPv6 addresses, CIDR prefixes, ASNs, TLS certificates, threat-feed indicators, DNSSEC posture, WHOIS records, and the full set of relationships between them — RESOLVES_TO, ANNOUNCED_BY, REGISTERED_BY, SHARES_NAMESERVER, LISTED_IN_FEED, and more. The schema covers ~20 entity types and is documented in the knowledge graph docs. **Q: How fast is Whisper?** A: Anchored Cypher queries against the graph typically return in single-digit milliseconds server-side. Multi-hop traversals across billions of edges still complete inside a request lifecycle — measured in tens of milliseconds, not seconds. The custom engine has zero garbage-collection pauses, which is what keeps tail latency tight. **Q: What does the API return?** A: The REST API accepts a Cypher query and returns JSON: columns, rows, and execution statistics. Each row contains the matched nodes and edges with their full property sets. Results stream when the response is large. You can also use the SDKs (Python, JavaScript) or query through the visual Console. **Q: Does Whisper have historical data?** A: Yes. The graph is time-aware. Edges carry validFrom and validTo timestamps for resolutions, BGP announcements, and certificate validity, so analysts can replay the past — "what did this domain resolve to on March 14?" or "which ASN announced this prefix six months ago?" — without leaving the graph. **Q: Can I run Whisper on-premises?** A: Yes. Whisper is available as cloud, dedicated cloud, or on-premises / air-gapped deployment for organisations with data-residency or isolation requirements. The graph engine and ingestion pipelines are the same in every deployment mode. Talk to us via the contact page. ### Pricing & Access **Q: Is there a free tier?** A: Yes. Sign up on the Console for the free Trial plan, which includes API and Console access with a monthly query allowance suitable for evaluation. Paid plans (Starter, Professional, Business, Enterprise) add higher quotas, MCP access, SIEM/SOAR connectors, and dedicated support. See the pricing page for current tiers. **Q: Do I need an API key to use Whisper?** A: Yes — every API and MCP request is authenticated. Generate a key from the Console after signing up. The MCP server uses the same key, passed as a bearer token. The Console also issues short-lived session tokens for browser use. **Q: What counts as a query?** A: One Cypher query against the API or MCP server counts as one query. The result size does not affect the count. Read-only operations and metadata calls are also counted. Quota and rate limits per plan are listed on the pricing page; operations that read large result sets are billed by row volume on higher tiers. **Q: Can I try Whisper without signing up?** A: You can run a few example queries against the public live demos on the homepage and the product page. Everything else — your own queries, MCP access, the API — requires a free account. Sign-up takes under a minute. ### Technology **Q: What is a graph database?** A: A graph database stores entities as nodes and the relationships between them as first-class edges. Traversals — "everything connected to X within N hops along these edges" — are cheap, where the same query in a relational database would force expensive recursive joins. Internet infrastructure is fundamentally a graph problem, which is why Whisper is built on one. **Q: Why Cypher and not SQL?** A: Cypher is purpose-built for graph queries. Its pattern syntax ((a)-[:EDGE]->(b)) maps directly to how analysts think about pivots. SQL forces graph problems through self-joins that get expensive fast. Cypher also has a structural advantage in the AI era: every major LLM already understands it, so AI agents connected via MCP generate correct queries from natural language with no fine-tuning. **Q: Did Whisper build its own graph engine?** A: Yes — the engine is custom. General-purpose graph databases could not handle the scale (~7B+ nodes, ~39B+ edges) at the latency we needed (single-digit milliseconds). The Whisper engine is in-memory, zero-GC, with native data types for IPv4, IPv6, CIDR, and ASN — no string-based work-arounds. Read more on the technology page. **Q: Where does the data come from?** A: Continuous BGP feeds, DNS observation (active and passive), WHOIS / RDAP, DNSSEC zone state, certificate transparency logs, Common Crawl, and 40+ threat-intelligence partners across 18 categories. Sources are joined into the graph in near-real-time so the picture stays current. **Q: Where is Whisper hosted?** A: The default cloud deployment runs in the EU (Germany), under European data jurisdiction. Dedicated cloud and on-premises deployments are available in other regions on request. ### Integrations & MCP **Q: Which AI agents work with Whisper?** A: Any MCP client — Claude Desktop, Cursor, VS Code, Continue, Windsurf, custom MCP-aware agents — can connect to the Whisper MCP server at mcp.whisper.security. The agent gains real-time access to the knowledge graph and can run Cypher queries, pivot between entities, and produce investigation reports. **Q: How do I connect Claude or Cursor to Whisper?** A: Add the Whisper MCP server to your client's configuration with your API key. The full step-by-step guide is in the MCP client setup docs. Most clients connect in under a minute. **Q: Which SIEMs does Whisper integrate with?** A: Native connectors are available for Splunk Enterprise (and Splunk Cloud), Microsoft Sentinel, OpenCTI, and Cortex XSOAR. The connectors handle authentication, query templates, and bidirectional data flow. Other SIEM/SOAR platforms can integrate via the REST API. See the integrations page. **Q: Is the MCP server publicly listed?** A: Yes — mcp.whisper.security is a public MCP endpoint, callable by any authenticated MCP client. Authentication uses the same API key as the REST API. **Q: Can I run my own MCP server pointing at Whisper?** A: Yes. Some teams prefer to run a self-hosted MCP relay that forwards queries to the Whisper API — useful when you need to add custom prompts, audit logging, or per-team scoping. The hosted MCP server works for most cases. ## Technical Documentation Each doc page below is followed by a "Markdown" URL (the citation-safe `.md` sibling) and an "HTML" URL (rendered for humans). Both serve the same content; AI assistants should prefer the `.md` form. ### Changelog Markdown: https://www.whisper.security/docs/changelog.md HTML: https://www.whisper.security/docs/changelog Notable changes to WhisperGraph: API behaviour, the MCP server, the Splunk integration, threat-intel feeds, and the graph schema. Subscribe via [RSS](https://www.whisper.security/docs/changelog/rss.xml.md) or check this page periodically. --- ## What gets changelogged - API endpoint additions, removals, and behaviour changes. - New or removed threat-intel [feeds](https://www.whisper.security/docs/reference/feed-catalog.md) and [categories](https://www.whisper.security/docs/reference/feed-categories.md). - MCP server tool changes. - Splunk add-on changes that affect search commands, dashboards, or modular inputs. - Graph schema changes — new node labels, edge types, or properties. Pure documentation polish is not changelogged. --- ## Subscribing - [RSS feed](https://www.whisper.security/docs/changelog/rss.xml.md) — for feed readers - Watch the [WhisperGraph Console](https://console.whisper.security) — release notes appear in the dashboard banner - Follow [@WhisperSecTech](https://x.com/WhisperSecTech) for high-impact releases --- *Entries will appear here as releases ship.* --- ### FAQ Markdown: https://www.whisper.security/docs/faq.md HTML: https://www.whisper.security/docs/faq Common questions about WhisperGraph and the documentation. If a question is missing, open a ticket at [console.whisper.security/support](https://console.whisper.security/support) or email [support@whisper.security](mailto:support@whisper.security). --- ## Why are some queries slow? The two most common causes: - **Unanchored MATCH** — `MATCH (h:HOSTNAME)` scans 2.6 billion nodes. Anchor with `{name: "..."}` or another indexed property. - **Fan-out without LIMIT** — `(:HOSTNAME)-[:RESOLVES_TO]->(:IPV4)` on a CDN hostname can return hundreds of rows per match. Default to `LIMIT 20`. See [Cypher Best Practices](https://www.whisper.security/docs/cypher-best-practices.md) for the full do-this/not-that table. --- ## What's the difference between physical and virtual edges? Physical edges are backed by stored rows from observations (e.g. `RESOLVES_TO` from a DNS observation). They support edge-property filters: `MATCH (a)-[r:RESOLVES_TO {firstSeen: "..."}]->(b)`. Virtual edges are computed at query time from indexed properties. `LISTED_IN`, `ANNOUNCED_BY`, `OPERATES`, `CONFLICTS_WITH` are virtual. You can traverse them like any edge, but you can't filter on edge properties (because there are none), the source node must be anchored, and they can't be used inside a variable-length pattern. See [Graph Schema](https://www.whisper.security/docs/reference/graph-schema.md) and [Glossary](https://www.whisper.security/docs/reference/glossary.md). --- ## How is the threat score computed? `explain()` returns a composite score from: - **Feed count and weights** — listed in 5 high-weight feeds scores higher than listed in 1 low-weight feed. - **Recency boost** — recent listings get a multiplier; stale listings decay. - **Network density** — IPs in dense malicious neighborhoods (other bad indicators in the same /24) score higher. The score is a weighted composite, not a fixed 0–10 scale — a heavily-listed network can score well into the dozens. The exact formula and weights evolve as feeds are added or rebalanced. Use the `factors` array in the `explain()` response to see how the score for a specific indicator was built, and branch automated rules on the `level` tier (`NONE` … `CRITICAL`) rather than the raw number. See [Cypher Functions & Procedures](https://www.whisper.security/docs/cypher-functions#features) for usage. --- ## Can I write to the graph? No. WhisperGraph is read-only via the public Cypher API. `CREATE`, `MERGE`, `SET`, and `DELETE` are not supported. The graph is populated by Whisper's ingestion pipeline (DNS, BGP, WHOIS, threat feeds) on a continuous basis. If you need to keep your own enrichment alongside graph data, store it in your environment (Splunk KV Store, your SIEM, a side database) and JOIN at query time. --- ## How fresh is the data? | Data source | Refresh cadence | |-------------|-----------------| | DNS observations (A, AAAA, CNAME, NS, MX) | Continuous, sub-minute on hot zones | | BGP routing | Continuous, real-time updates from public collectors | | WHOIS | Daily | | Threat-intel feeds | Hourly incremental, daily full refresh — see [Feed Catalog](https://www.whisper.security/docs/reference/feed-catalog.md) | | RPKI status | Hourly | | GeoIP | Weekly | The `whisper.history()` procedure returns timestamped snapshots so you can see how data evolved over time. --- ## What happens if my API key isn't being recognised? If your key is missing, malformed, or revoked, requests run on the unauthenticated tier — they don't return 401. Symptoms: queries that worked previously now hit a depth limit, or rate limits look tighter than expected. Fix: run `CALL whisper.quota()` to see which tier you're on, then check your key in the [console](https://console.whisper.security). For tier limits see the [pricing page](https://www.whisper.security/pricing). --- ## What happens at quota? Quota is enforced on an hourly and a daily window. When you exceed it you get a 429, and the response carries the rate-limit headers: - `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` — your hourly quota, queries left this hour, and when the hourly window resets. - `X-DailyLimit-Limit` / `X-DailyLimit-Remaining` / `X-DailyLimit-Reset` — the matching values for the daily window. Wait until the relevant reset time and retry, or run `CALL whisper.quota()` to see your current usage. For per-plan limits, see the [pricing page](https://www.whisper.security/pricing). --- ## Why isn't `whisper.history()` returning anything for my subdomain? History is keyed on registrable domains. `whisper.history("www.example.com")` returns nothing; use `whisper.history("example.com")` and inspect subdomain structure through the graph instead — there is no `DOMAIN` label or `SUBDOMAIN_OF` edge, so walk the hostname hierarchy with `CHILD_OF`: ```cypher MATCH (sub:HOSTNAME)-[:CHILD_OF*1..3]->(parent:HOSTNAME {name: "example.com"}) RETURN sub.name LIMIT 50 ``` --- ## Are there language-specific SDKs? Not yet — official Python and JavaScript SDKs are on the roadmap. In the meantime, see the [Python and JavaScript examples](https://www.whisper.security/docs/getting-started#use-a-client-library) in Getting Started. Community contributions welcome. --- ## How do I report a bug or ask for help? See [How to ask for help](https://www.whisper.security/docs/support.md). --- ### Cypher Query Cookbook Markdown: https://www.whisper.security/docs/cypher-query-cookbook.md HTML: https://www.whisper.security/docs/cypher-query-cookbook Copy-paste Cypher recipes for common security investigations on WhisperGraph. Each recipe is self-contained — swap in your own indicators and run against the [Cypher API](https://www.whisper.security/docs/cypher-api-reference.md). For language syntax and schema details, see the [Cypher Language Reference](https://www.whisper.security/docs/cypher-query-guide.md). The cookbook is now organized by persona. Pick the workflow that matches what you're trying to do, or jump to the [Cypher Cheat Sheet](https://www.whisper.security/docs/cheat-sheet.md) for the one-page summary. --- ## Recipes by persona - [SOC Analysts & Incident Responders](https://www.whisper.security/docs/recipes/soc.md) — Triage alerts, trace IPs to networks, investigate hostnames, pivot through DNS, and run a full IOC investigation in one query. - [Threat Intelligence Analysts](https://www.whisper.security/docs/recipes/threat-intel.md) — Hunt across 39 threat feeds, score indicators, follow campaign infrastructure, and map operator footprints. - [Penetration Testers & Red Teams](https://www.whisper.security/docs/recipes/pentest-recon.md) — External recon: subdomain discovery, infrastructure enumeration, exposed services, and lateral pivots through shared hosting. - [Brand Protection & Anti-Phishing](https://www.whisper.security/docs/recipes/brand-protection.md) — Find typosquats, lookalike domains, fraudulent registrations, and infrastructure used for credential theft. - [DNS & Email Security](https://www.whisper.security/docs/recipes/dns-email.md) — Trace SPF chains, DNSSEC validation, MX records, CNAME hops, and detect dangling DNS records. - [Network & BGP Security](https://www.whisper.security/docs/recipes/bgp-routing.md) — Detect BGP hijacks, MOAS conflicts, route flux, RPKI status, and trace ASN peering relationships. - [Compliance & Risk Assessment](https://www.whisper.security/docs/recipes/compliance.md) — Audit DNS posture, SPF/DMARC adoption, geographic hosting jurisdiction, and surface compliance gaps for vendors. - [Cyber Insurance & Third-Party Risk](https://www.whisper.security/docs/recipes/insurance-risk.md) — Score third-party infrastructure exposure, identify shared providers, and quantify supply-chain blast radius. - [Law Enforcement & Cybercrime](https://www.whisper.security/docs/recipes/law-enforcement.md) — Pivot from indicators to operators using WHOIS, registrar relationships, hosting providers, and historical infrastructure. - [Security Researchers & Academics](https://www.whisper.security/docs/recipes/research.md) — Bulk infrastructure surveys, dataset extraction, longitudinal studies, and reproducible research queries. - [Cross-Cutting Recipes](https://www.whisper.security/docs/recipes/cross-cutting.md) — Recipes that span personas: full-context investigations, threat-score breakdowns, and multi-step pivots. --- ## 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: (optional — anonymous access is available) {"query": "MATCH (h:HOSTNAME {name: 'example.com'}) RETURN h.name"} ``` Every response has three fields: ```json { "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: ```cypher // Confirm a hostname is indexed MATCH (h:HOSTNAME {name: "google.com"}) RETURN h.name LIMIT 1 ``` **Sample output** (from live validation): ```json [{"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 ```cypher // DNS A record lookup MATCH (h:HOSTNAME {name: "www.google.com"})-[:RESOLVES_TO]->(ip:IPV4) RETURN h.name, ip.name LIMIT 20 ``` **Sample output**: ```json [ {"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 ```cypher // View your current quota and plan tier CALL whisper.quota() ``` **Sample output**: ```json [ {"key": "plan", "value": "PROFESSIONAL"}, {"key": "isAnonymous", "value": false}, {"key": "hourlyRemaining", "value": 9850}, {"key": "dailyRemaining", "value": 48200}, {"key": "maxQueryDepth", "value": 5} ] ``` --- ## See also - [Cypher API Reference](https://www.whisper.security/docs/cypher-api-reference.md) — endpoints, parameters, errors - [Cypher Language Reference](https://www.whisper.security/docs/cypher-query-guide.md) — full syntax and schema - [Cypher Cheat Sheet](https://www.whisper.security/docs/cheat-sheet.md) — one-page summary --- ### Introduction to Whisper Knowledge Graph Markdown: https://www.whisper.security/docs/whisper-graph-intro.md HTML: https://www.whisper.security/docs/whisper-graph-intro WhisperGraph maps the global internet into a single queryable graph. DNS records, IP addresses, BGP routing, WHOIS registration, web links, threat-intelligence feeds — all connected, all searchable, all in one place. You query it with Cypher over a REST API at `https://graph.whisper.security`. See the [Getting Started](https://www.whisper.security/docs/getting-started.md) guide to run your first query, the [Cypher Language Reference](https://www.whisper.security/docs/cypher-query-guide.md) for the full schema and syntax, or the [Query Cookbook](https://www.whisper.security/docs/cypher-query-cookbook.md) for ready-to-run recipes by use case. --- ## The graph at a glance | Layer | Scale | |-------|-------| | **Hostnames** | 2.6 billion | | **IP addresses** | 619 million IPv4 • 820K IPv6 | | **WHOIS contact emails / phones** | 237M / 60M | | **Web hyperlinks** (Common Crawl) | 10.85 billion `LINKS_TO` edges | | **Nameserver edges** | 8.88 billion `NAMESERVER_FOR` | | **DNS resolution edges** | 2.92 billion `RESOLVES_TO` | | **BGP announcements** | 3.68 billion `ANNOUNCED_BY` (refreshed live) | | **Threat-intel listings** | ~5.3 million `LISTED_IN` across 39 feeds, 18 categories | | **ASNs** | 116K (with 457K peering relationships) | | **Total** | **~7.4 billion nodes • ~39 billion edges** | These aren't aggregates over time — they're the current state of the graph, refreshed continuously. --- ## Why a graph? Internet infrastructure is naturally a graph: hostnames link to IPs, IPs sit inside prefixes, prefixes are routed by ASNs, ASNs peer with others. The same domain might share a WHOIS registrant email with twenty others, all hosted in the same ASN as a known-bad domain. With a flat lookup API, that pivot is five separate calls and a lot of glue code. With Cypher over a graph, it's one query: ```cypher // An IP attribution chain in one round-trip: // IP → announced prefix → ASN → ASN name → country MATCH (ip:IPV4 {name: "8.8.8.8"}) -[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX) -[:ROUTES]->(a:ASN)-[:HAS_NAME]->(n:ASN_NAME) MATCH (ip)-[:HAS_COUNTRY]->(c:COUNTRY) RETURN ap.name AS prefix, a.name AS asn, n.name AS network, c.name AS country ``` That's the difference WhisperGraph makes. --- ## How it works Every entity on the internet becomes a node. Every observed relationship becomes an edge. A hostname resolves to an IP. That IP sits inside a network prefix. That prefix is routed by an autonomous system. That AS peers with others, operates in a country, and may show up in threat feeds. WhisperGraph captures all of it. ![WhisperGraph overview](https://whisper.cdn.prismic.io/whisper/adRK4ZGXnQHGZTDr_graph-overview.svg) The graph contains billions of nodes and tens of billions of edges, covering DNS, BGP routing, WHOIS registration, GeoIP, web hyperlinks, SPF/DNSSEC configurations, and 39 threat-intelligence feeds. --- ## What's in the graph ### DNS & Web - `HOSTNAME` (2.6B), `TLD` (1.7K), `TLD_OPERATOR` (737) - `RESOLVES_TO` (2.9B A/AAAA records), `CHILD_OF` (subdomain hierarchy), `ALIAS_OF` (CNAMEs, 434M), `NAMESERVER_FOR` (8.88B), `MAIL_FOR` (562M MX records) - 10.85 **billion** `LINKS_TO` edges from a continuously updated Common Crawl ingest — the open web's hyperlink graph, in the same query surface as DNS and WHOIS ### IP & routing (live BGP) - `IPV4` (619M), `IPV6` (820K), `PREFIX` (2.5M), `ANNOUNCED_PREFIX` (1.4M), `REGISTERED_PREFIX` (325K), `ASN` (116K), `ASN_NAME` (108K), `RIR` (5) - `BELONGS_TO`, `ANNOUNCED_BY` (3.68B current announcements), `ROUTES`, `PEERS_WITH` (457K peerings), `HAS_NAME` - `CONFLICTS_WITH` — surfaces MOAS conflicts (the same prefix announced by multiple origin ASNs), an early signal of BGP hijacks ### Registration (WHOIS at pivot scale) - `REGISTRAR` (50K), `ORGANIZATION` (119M), `EMAIL` (237M), `PHONE` (60M) - `HAS_REGISTRAR` (649M) + `PREV_REGISTRAR` (618M) — current and historical; track registrar transfers - `HAS_EMAIL` (547M), `HAS_PHONE` (550M), `REGISTERED_BY` (916M) — pivot from one bad domain to every domain sharing its registrant ### Geography - `COUNTRY` (424), `CITY` (54K) - `LOCATED_IN` (118M), `HAS_COUNTRY` (195M) ### Email & DNSSEC posture - Six SPF edge types: `SPF_INCLUDE`, `SPF_IP`, `SPF_A`, `SPF_MX`, `SPF_EXISTS`, `SPF_REDIRECT` (~600M edges total) — walk the full SPF authorization tree - `DNSSEC_ALGORITHM` (8 algorithms tracked), `SIGNED_WITH` for zone-by-zone DNSSEC posture ### Threat intelligence - 39 `FEED_SOURCE` nodes across 18 `CATEGORY` nodes - `LISTED_IN` (~5.3M edges) connecting indicators (IPv4, hostnames) to the feeds that flag them - See [Feed Catalog](https://www.whisper.security/docs/reference/feed-catalog.md) and [Feed Categories](https://www.whisper.security/docs/reference/feed-categories.md) for the full breakdown. --- ## What you can traverse The graph supports rich traversals across every layer. Anchor on an indexed property, then follow whatever combination of edges fits the investigation. Anchored queries return in milliseconds — even when the traversal counts billions of candidate edges. ### DNS resolution and hierarchy - **A / AAAA records** — `(host)-[:RESOLVES_TO]->(ip)` (2.92B edges) - **CNAME chains, walkable end-to-end** — `(host)-[:ALIAS_OF*1..5]->(target)` (434M edges). `www.netflix.com` resolves through three AWS ELB hops. - **Subdomain trees** — `(parent)<-[:CHILD_OF*1..N]-(sub)` (2.34B edges). `cloudflare.com` has 6,054 observed subdomains; you can enumerate them at any depth. - **Suffix and prefix matching** — `WHERE h.name STARTS WITH "paypal-"` or `ENDS WITH ".cloudflare.com"` are both indexed and fast — useful for typosquat hunting and bulk subdomain audits. ### Web link graph (Common Crawl) - **Inbound and outbound links** — `(src)-[:LINKS_TO]->(target)` (10.85 **billion** edges). Walk in either direction: - Who links *to* this site → `(src)-[:LINKS_TO]->(h:HOSTNAME {name:"whisper.security"})` - What does this site link *to* → `(h:HOSTNAME {name:"cloudflare.com"})-[:LINKS_TO]->(target)` - **Topical clustering** — combine LINKS_TO with WHOIS or BGP to find sites that link to the same hosts and share an ASN or registrant. - **Web graph as a threat signal** — pages linking to listed-bad hostnames, or pages linked from inside a phishing cluster, are typically just one more hop in the same Cypher query. ### IP attribution and routing (live BGP) - **5-hop attribution** — `(host)-[:RESOLVES_TO]->(ip)-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)-[:ROUTES]->(asn:ASN)-[:HAS_NAME]->(name:ASN_NAME)`. From a hostname to the operator's name in a single round-trip. - **RIR allocation chain** — `(ip)-[:BELONGS_TO]->(rp:REGISTERED_PREFIX)-[:HAS_COUNTRY]->(country)` and `(rp)-[:BELONGS_TO]->(rir:RIR)`. - **BGP peering** — `(asn)-[:PEERS_WITH]-(peer)` (457K observed peerings). Map an ASN's transit and customer relationships. - **MOAS conflicts and hijack signals** — `(prefix)-[:CONFLICTS_WITH]-(other)` (~15.5K edges). Surfaces prefixes announced by multiple origin ASNs — the leading early indicator of route hijacks. ### Geography - **GeoIP** — `(ip)-[:LOCATED_IN]->(city)-[:HAS_COUNTRY]->(country)` (118M LOCATED_IN, 195M HAS_COUNTRY). - **ASN home country** — `(asn)-[:HAS_COUNTRY]->(country)` for compliance / jurisdiction questions. ### Email and DNS records - **Mail servers** — `(mx:HOSTNAME)-[:MAIL_FOR]->(host)` (562M MX edges). The MX record is the source, the served domain is the target. - **Authoritative nameservers** — `(ns:HOSTNAME)-[:NAMESERVER_FOR]->(host)` (8.88B edges). Same direction: NS → host. - **Full SPF authorization tree** — six edge types, walkable as a single union: `(host)-[:SPF_INCLUDE | :SPF_IP | :SPF_A | :SPF_MX | :SPF_EXISTS | :SPF_REDIRECT]->(target)`. Trace every `include:` and IP allow-list back to its origin. - **DNSSEC posture** — `(host)-[:SIGNED_WITH]->(alg:DNSSEC_ALGORITHM)`. Eight algorithms tracked. ### WHOIS pivots (current and historical) - **Direct attributes** — `(host)-[:HAS_REGISTRAR]->(r)`, `-[:HAS_EMAIL]->(e)`, `-[:HAS_PHONE]->(p)`, `-[:REGISTERED_BY]->(o:ORGANIZATION)`. - **Historical** — `(host)-[:PREV_REGISTRAR]->(prior)` (618M edges) tracks every registrar transfer. - **Sibling pivot** — anchor on the contact node, walk back: `(e:EMAIL {name:"..."})<-[:HAS_EMAIL]-(sibling:HOSTNAME)`. One query surfaces every domain registered with the same email — the canonical phishing-cluster discovery pattern. ### Registry / TLD - **Who operates a TLD** — `(operator:TLD_OPERATOR)-[:OPERATES]->(tld:TLD)`. Useful for jurisdictional and policy questions ("who runs `.gov`, `.bank`, `.example`?"). ### Threat intelligence - **Indicator → feeds → categories** — `(indicator)-[:LISTED_IN]->(feed:FEED_SOURCE)` works for IPs, hostnames, ASNs, and CIDR ranges. Feeds carry their own categories for filtering. - **Composite scoring in one call** — `CALL explain("indicator")` traverses all listings and returns a single weighted score with its factors and source list (see the [threat-intel section](#threat-intelligence)). ### Going deep — multi-hop and cross-layer The real power is combining layers in a single Cypher query. **Variable-length expansion** for tree walks: ```cypher // Every subdomain four levels deep MATCH (sub:HOSTNAME)-[:CHILD_OF*1..4]->(p:HOSTNAME {name: "cloudflare.com"}) RETURN sub.name LIMIT 100 ``` **WHOIS → BGP** to map an organisation's network footprint: ```cypher MATCH (h:HOSTNAME {name: "github.com"})-[:RESOLVES_TO]->(:IPV4) -[:ANNOUNCED_BY]->(:ANNOUNCED_PREFIX) -[:ROUTES]->(a:ASN)-[:HAS_NAME]->(n:ASN_NAME) RETURN DISTINCT a.name, n.name ``` ```json [ {"a.name": "AS36459", "n.name": "GITHUB - GitHub, Inc."}, {"a.name": "AS8075", "n.name": "MICROSOFT-CORP-MSN-AS-BLOCK - Microsoft Corporation"} ] ``` GitHub answers from both its own AS and Microsoft's — visible in one query. **Index-anchored pattern hunting** for typosquats: ```cypher // Every paypal-* hostname (10 returned, all obvious lookalikes) MATCH (h:HOSTNAME) WHERE h.name STARTS WITH "paypal-" RETURN h.name LIMIT 10 ``` **Cross-layer combinations** (sketches, swap in your own anchors): - *Web → threat:* `(:HOSTNAME)-[:LINKS_TO]->(t:HOSTNAME)-[:LISTED_IN]->(:FEED_SOURCE)` — pages linking to flagged hosts. - *Email → BGP:* `(:EMAIL {name:"..."})<-[:HAS_EMAIL]-(:HOSTNAME)-[:RESOLVES_TO]->(:IPV4)-[:ANNOUNCED_BY]->(:ANNOUNCED_PREFIX)-[:ROUTES]->(a:ASN)` — every ASN hosting infrastructure registered with a given contact email. - *DNSSEC + portfolio audit:* `MATCH (h:HOSTNAME) WHERE h.name ENDS WITH ".bank" AND NOT (h)-[:SIGNED_WITH]->(:DNSSEC_ALGORITHM)` — `.bank` zones missing DNSSEC. The [Cypher Cookbook](https://www.whisper.security/docs/cypher-query-cookbook.md) has copy-paste recipes for each pattern, organised by security workflow. --- ## Threat intelligence WhisperGraph indexes 39 threat-intelligence feeds across 18 categories. IPs, hostnames, ASNs, and CIDR ranges that appear in feeds connect to `FEED_SOURCE` nodes via `LISTED_IN` edges. Each `FEED_SOURCE` belongs to one or more categories (e.g. "C2 Servers", "Phishing", "General Blacklists"). **Trust signals, not just block lists.** Cloudflare Radar Top 1M and Tranco Top 1M are integrated alongside Spamhaus, Feodo Tracker, and the rest — so the graph answers *"is this known-good?"* as well as *"is this known-bad?"*. ```cypher // Check which threat feeds list an IP MATCH (ip:IPV4 {name: "185.220.101.1"})-[:LISTED_IN]->(f:FEED_SOURCE) RETURN ip.name, f.name // Get a scored threat assessment CALL explain("185.220.101.1") ``` `explain()` accepts five indicator types: IP, domain, ASN, CIDR prefix, and file hash. The composite score is built as `weight × log₂(feeds + 1) × recency_boost` — the response includes a `factors` array showing exactly how it was computed, plus a `sources` list of contributing feeds with first/last-seen timestamps. ASN nodes also carry aggregate threat statistics across all their routed prefixes, so `CALL explain("AS60729")` gives you a one-call reputation snapshot for an entire network. --- ## Historical data `CALL whisper.history()` returns timestamped WHOIS and BGP routing history for an indicator. Supported indicator types: domain, IP, ASN, and CIDR prefix. For domains, that's registrar changes, nameserver changes, and registration dates over time: ```json { "indicator": "cloudflare.com", "type": "domain", "createDate": "2009-02-17", "registrar": "CloudFlare, Inc.", "nameServers": "..." } ``` For IPs, prefixes, and ASNs, it returns BGP origin changes, prefix announcements, and withdrawals — useful for hijack forensics and tracking infrastructure handoffs. History is keyed on registrable domains (use `cloudflare.com`, not `www.cloudflare.com`). The procedure fetches live data and may return `available: false` with `error: "timeout"` and a `retryAfter` value — wait the suggested seconds and retry. --- ## Schema introspection You don't have to know the schema up front — query it. ```cypher CALL db.labels() // 20 node labels with row counts CALL db.relationshipTypes() // 24 edge types with row counts CALL db.propertyKeys() // every property key in the graph CALL db.schema() // full schema snapshot ``` Each procedure returns instantly regardless of graph size — they read from indexed histograms, not the data itself. `CALL whisper.quota()` shows the current request's plan, usage, and per-request caps. See [Cypher Functions & Procedures](https://www.whisper.security/docs/cypher-functions.md) for the full procedure catalogue. --- ## Use cases Pick the workflow that matches what you're trying to do — every persona below has a dedicated [use case page](https://www.whisper.security/docs/cypher-query-cookbook.md) with copy-paste recipes. - **[SOC analysts and incident responders](https://www.whisper.security/docs/recipes/soc.md)** — triage suspicious IPs, trace network ownership, check threat feeds, build evidence chains. - **[Threat intelligence analysts](https://www.whisper.security/docs/recipes/threat-intel.md)** — map campaign infrastructure through shared hosting, WHOIS pivots, and nameserver clustering. - **[Penetration testers and red teams](https://www.whisper.security/docs/recipes/pentest-recon.md)** — passive reconnaissance: subdomains, IP ranges, mail servers, SPF chains, CNAME following. - **[Brand protection teams](https://www.whisper.security/docs/recipes/brand-protection.md)** — find lookalike domains, map phishing clusters, trace shared infrastructure. - **[DNS and email security engineers](https://www.whisper.security/docs/recipes/dns-email.md)** — audit nameservers, trace SPF chains, check DNSSEC, batch-audit domain portfolios. - **[Network and BGP engineers](https://www.whisper.security/docs/recipes/bgp-routing.md)** — profile ASN footprints, analyse peering, track announcements, investigate allocations. - **[Compliance and risk teams](https://www.whisper.security/docs/recipes/compliance.md)** — verify registrars, check jurisdictional exposure, build security profiles, pull WHOIS history. - **[Cyber insurance and third-party risk](https://www.whisper.security/docs/recipes/insurance-risk.md)** — external posture snapshots, exposure checks, hosting identification, threat scores. - **[Law enforcement investigators](https://www.whisper.security/docs/recipes/law-enforcement.md)** — IP attribution chains, ownership records, related-domain discovery, timestamped evidence. - **[Security researchers](https://www.whisper.security/docs/recipes/research.md)** — graph schema exploration, internet topology studies, peering degree distributions, web graph properties. - **[Cross-cutting recipes](https://www.whisper.security/docs/recipes/cross-cutting.md)** — full-context investigations, threat-score breakdowns, multi-step pivots that span personas. --- ## How WhisperGraph is different - **Cypher, not REST joins.** Real graph queries with traversal, pattern matching, shortest-path, and variable-length expansion. - **Continuously refreshed BGP, threat intel, and MOAS conflicts** — the answer reflects what's true now, not yesterday's snapshot. - **Web-scale link graph.** 10.85 billion `LINKS_TO` edges from Common Crawl, in the same graph as DNS and WHOIS. - **WHOIS at pivot scale.** 237M emails, 60M phones, current and historical registrars across 618M edges. - **Trust signals as a first-class feed type.** Whitelist feeds (Cloudflare Radar Top 1M, Tranco Top 1M) live alongside block lists — you can answer "is this known-good?" not just "is this known-bad?". - **Schema introspection.** `CALL db.*` exposes labels, edge types, property keys, and full schema snapshots so you can explore without external docs. --- ## Try it now Run your first query right now — anonymous access works without an API key, useful for a quick sanity check before signing up: ```bash curl -s https://graph.whisper.security/api/query \ -H "Content-Type: application/json" \ -d '{"query":"MATCH (h:HOSTNAME {name:\"www.google.com\"})-[:RESOLVES_TO]->(ip) RETURN h.name, ip.name LIMIT 3"}' ``` Then go [build something real](https://www.whisper.security/docs/getting-started.md). Stuck? See the [FAQ](https://www.whisper.security/docs/faq.md), [Glossary](https://www.whisper.security/docs/reference/glossary.md), or [How to ask for help](https://www.whisper.security/docs/support.md). --- ### Graph Schema Reference Markdown: https://www.whisper.security/docs/reference/graph-schema.md HTML: https://www.whisper.security/docs/reference/graph-schema Reference for the WhisperGraph schema: every node label, every edge type, and the multi-hop patterns that connect them. Use this alongside the [Cypher syntax reference](https://www.whisper.security/docs/cypher-syntax.md) when writing queries. ## Schema guide WhisperGraph has two kinds of structure. Most nodes and edges are stored directly. A second set — the virtual infrastructure and threat-intelligence layers — is synthesized at query time from live data. Virtual edges behave like stored edges in almost every way, with one rule that matters: **you must anchor the source node before traversing a virtual edge.** More on that under [Edge types](#edge-types). There is no `DOMAIN` or `FQDN` label. Every DNS name — including nameservers and mail servers — is a `HOSTNAME`. ## Node labels | Label | Description | Example values | |-------|-------------|----------------| | HOSTNAME | Fully-qualified domain names, subdomains, mail server names | `www.google.com`, `ns1.cloudflare.com` | | IPV4 | IPv4 addresses | `1.1.1.1`, `142.250.64.100` | | IPV6 | IPv6 addresses | `2606:4700::6810:84e5` | | PREFIX | IP CIDR blocks from DNS and RIR imports | `142.250.64.0/24` | | REGISTERED_PREFIX | RIR-allocated IP blocks (virtual, resolved at query time) | `1.1.1.0/24` | | ANNOUNCED_PREFIX | BGP-announced prefixes (virtual, resolved at query time) | `104.16.128.0/20` | | ASN | Autonomous system numbers | `AS13335`, `AS15169` | | ASN_NAME | Human-readable AS organization names | `CLOUDFLARENET - Cloudflare, Inc.` | | TLD | Top-level domains | `com`, `net`, `org`, `io` | | TLD_OPERATOR | TLD registry operators | `VeriSign Global Registry Services` | | REGISTRAR | Domain registrars (IANA ID format) | `iana:292` (MarkMonitor) | | EMAIL | WHOIS contact email addresses | `domains@cloudflare.com` | | PHONE | WHOIS contact phone numbers (E.164) | `+14158675825` | | ORGANIZATION | Organizations from WHOIS or RIR records | `cloudflare hostmaster` | | CITY | GeoIP city with country code | `Mountain View, US` | | COUNTRY | ISO 3166-1 alpha-2 country codes | `US`, `DE`, `AU` | | RIR | Regional Internet Registries | `ARIN`, `RIPENCC`, `APNIC`, `LACNIC`, `AFRINIC` | | DNSSEC_ALGORITHM | DNSSEC signing algorithms | `ECDSAP256SHA256`, `RSASHA256` | | FEED_SOURCE | Threat intelligence feed sources (virtual) | `Spamhaus DROP`, `Feodo Tracker` | | CATEGORY | Threat feed categories (virtual) | `C2 Servers`, `Phishing` | `CALL db.labels()` returns all 20 labels with live row counts. ## Edge types Edges have a direction. A wrong-direction traversal returns zero rows with no error, so direction matters — you can always traverse an edge backwards by writing `<-[:TYPE]-`. The tables below give the storage direction. **DNS resolution** | Edge type | From | To | Description | |-----------|------|----|-------------| | RESOLVES_TO | HOSTNAME | IPV4/IPV6 | DNS A/AAAA records | | CHILD_OF | HOSTNAME | HOSTNAME/TLD | Domain hierarchy (sub.example.com → example.com → com) | | ALIAS_OF | HOSTNAME | HOSTNAME | CNAME records | | NAMESERVER_FOR | HOSTNAME | HOSTNAME | NS delegation (the nameserver serves the target domain) | | MAIL_FOR | HOSTNAME | HOSTNAME | MX records (the mail server handles mail for the target domain) | | SIGNED_WITH | HOSTNAME | DNSSEC_ALGORITHM | DNSSEC signing algorithm | **BGP and routing** | Edge type | From | To | Description | |-----------|------|----|-------------| | ANNOUNCED_BY | IPV4/IPV6 | ANNOUNCED_PREFIX | The IP falls inside a BGP-announced prefix (virtual) | | ROUTES | ASN | PREFIX/ANNOUNCED_PREFIX | The ASN routes the prefix (virtual) | | BELONGS_TO | IPV4/IPV6 | PREFIX/REGISTERED_PREFIX | The IP belongs to an allocation block | | PEERS_WITH | ASN | ASN | BGP peering relationship (virtual) | | HAS_NAME | ASN | ASN_NAME | Network operator name (virtual) | | CONFLICTS_WITH | PREFIX | ASN | A multi-origin AS (MOAS) conflict — the same prefix announced by more than one ASN (virtual) | **WHOIS and registration** | Edge type | From | To | Description | |-----------|------|----|-------------| | HAS_REGISTRAR | HOSTNAME | REGISTRAR | Current domain registrar | | PREV_REGISTRAR | HOSTNAME | REGISTRAR | A historical, superseded registrar | | REGISTERED_BY | HOSTNAME/ASN/PREFIX | ORGANIZATION | WHOIS / RIR registrant organization | | HAS_EMAIL | HOSTNAME | EMAIL | WHOIS contact email | | HAS_PHONE | HOSTNAME | PHONE | WHOIS contact phone | **Geo** | Edge type | From | To | Description | |-----------|------|----|-------------| | LOCATED_IN | IPV4/IPV6 | CITY | GeoIP city for the IP. `LOCATED_IN` never targets COUNTRY — chain through `HAS_COUNTRY` to reach a country | | HAS_COUNTRY | ASN/CITY/IPV4/HOSTNAME/PHONE/ANNOUNCED_PREFIX/REGISTERED_PREFIX | COUNTRY | The entity's country | **Threat intelligence** | Edge type | From | To | Description | |-----------|------|----|-------------| | LISTED_IN | IPV4/IPV6/HOSTNAME | FEED_SOURCE | The indicator appears in this threat feed (virtual) | | BELONGS_TO | FEED_SOURCE | CATEGORY | The feed is classified under this category | **Web** | Edge type | From | To | Description | |-----------|------|----|-------------| | LINKS_TO | HOSTNAME | HOSTNAME | A web hyperlink from one site to another (from web crawl data) | **SPF** | Edge type | From | To | Description | |-----------|------|----|-------------| | SPF_INCLUDE | HOSTNAME | HOSTNAME | SPF `include:` mechanism | | SPF_IP | HOSTNAME | IPV4/IPV6/PREFIX | SPF `ip4:` / `ip6:` mechanism | | SPF_A | HOSTNAME | HOSTNAME | SPF `a:` mechanism | | SPF_MX | HOSTNAME | HOSTNAME | SPF `mx:` mechanism | | SPF_REDIRECT | HOSTNAME | HOSTNAME | SPF `redirect=` modifier | | SPF_EXISTS | HOSTNAME | HOSTNAME | SPF `exists:` mechanism | **Registry** | Edge type | From | To | Description | |-----------|------|----|-------------| | OPERATES | TLD_OPERATOR | TLD | The registry operator runs the top-level domain (virtual) | ### Virtual edges must be anchored Some edges are physical (stored directly), some are virtual (synthesized at query time), and a few are a mix. The virtual and mixed edges — `ANNOUNCED_BY`, `ROUTES` into `ANNOUNCED_PREFIX`, `HAS_NAME`, `BELONGS_TO` into `REGISTERED_PREFIX`, `LISTED_IN`, `CONFLICTS_WITH`, and `OPERATES` — are only produced when the source node of the traversal is anchored. An unanchored probe like `MATCH (a:ASN)-[:HAS_NAME]->(n)` returns nothing; the anchored form `MATCH (a:ASN {name: "AS13335"})-[:HAS_NAME]->(n)` returns `CLOUDFLARENET - Cloudflare, Inc.` Always anchor before traversing a virtual edge, and never put a virtual edge inside a variable-length pattern. To chain two virtual edges (for example `LISTED_IN` into `BELONGS_TO`), insert a `WITH` between them so the intermediate node is materialised before the second hop. ## Entity relationship diagram ![Entity Relationship Diagram](https://whisper.cdn.prismic.io/whisper/adRK45GXnQHGZTDs_entity-relationship-map.svg) *Solid lines are physical edges stored on disk. Dashed lines are virtual edges computed at query time from live infrastructure and threat intelligence data.* ## Multi-hop path patterns These are the most common traversal chains through the graph. **Domain to network owner:** ``` HOSTNAME -[:RESOLVES_TO]-> IPV4 -[:ANNOUNCED_BY]-> ANNOUNCED_PREFIX -[:ROUTES]-> ASN -[:HAS_NAME]-> ASN_NAME ``` **Domain to nameservers:** ``` HOSTNAME(ns) -[:NAMESERVER_FOR]-> HOSTNAME(domain) ``` **Domain to mail servers:** ``` HOSTNAME(mx) -[:MAIL_FOR]-> HOSTNAME(domain) ``` **IP to GeoIP location:** ``` IPV4 -[:LOCATED_IN]-> CITY -[:HAS_COUNTRY]-> COUNTRY ``` **IP to threat feeds:** ``` IPV4 -[:LISTED_IN]-> FEED_SOURCE -[:BELONGS_TO]-> CATEGORY ``` **Domain WHOIS chain:** ``` HOSTNAME -[:HAS_REGISTRAR]-> REGISTRAR HOSTNAME -[:HAS_EMAIL]-> EMAIL HOSTNAME -[:REGISTERED_BY]-> ORGANIZATION ``` **DNS hierarchy:** ``` HOSTNAME -[:CHILD_OF]-> HOSTNAME(parent) -[:CHILD_OF]-> TLD ``` **RIR allocation chain:** ``` IPV4 -[:BELONGS_TO]-> REGISTERED_PREFIX -[:REGISTERED_BY]-> ORGANIZATION ``` **Registry operator:** ``` TLD_OPERATOR -[:OPERATES]-> TLD ``` --- ### Cypher API Reference Markdown: https://www.whisper.security/docs/cypher-api-reference.md HTML: https://www.whisper.security/docs/cypher-api-reference All queries go to `https://graph.whisper.security`. Authenticate with an API key — see [Getting Started](https://www.whisper.security/docs/getting-started.md) if you don't have one yet. Anonymous access (no key) is available with reduced limits. --- ## Anonymous access Unauthenticated requests are accepted and run at the lowest tier — useful for quick exploration. For higher quotas and deeper traversals, send your API key in the `X-API-Key` header. Per-plan limits live on the [pricing page](https://www.whisper.security/pricing). Run `CALL whisper.quota()` at any time to see the tier the request is using and the per-request caps that apply (max query depth, default and max timeouts, response-row limit, concurrent-query limit). ## Query endpoint (POST) The primary query endpoint. Send a Cypher query as JSON. | | | |---|---| | Method | `POST` | | URL | `/api/query` | | Headers | `Content-Type: application/json`, `X-API-Key: $API_KEY` | **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `query` | string | Yes | Cypher query | | `parameters` | object | No | Named parameters for parameterised queries | | `timeout` | long | No | Per-request timeout override in milliseconds. Capped at the plan's `maxTimeoutMs` (visible via `CALL whisper.quota()`). | ```bash curl -s -X POST $BASE/api/query \ -H "Content-Type: application/json" \ -H "X-API-Key: $API_KEY" \ -d '{"query": "MATCH (n:ASN) RETURN n.name LIMIT 3"}' ``` Response: ```json { "columns": ["n.name"], "rows": [{"n.name": "AS1"}, {"n.name": "AS10"}, {"n.name": "AS100"}], "statistics": {"rowCount": 3, "executionTimeMs": 0} } ``` Every response has the same shape: `columns` (the projection), `rows` (each row is an object keyed by column expression), and `statistics` with `rowCount` and server-side `executionTimeMs`. ### Parameterized queries Use `$paramName` in your query and pass values in the `parameters` object. Parameters keep your query plan cacheable and avoid quoting headaches: ```bash curl -s -X POST $BASE/api/query \ -H "Content-Type: application/json" \ -H "X-API-Key: $API_KEY" \ -d '{"query": "MATCH (h:HOSTNAME {name: $host}) RETURN h.name", "parameters": {"host": "www.google.com"}}' ``` ```json { "columns": ["h.name"], "rows": [{"h.name": "www.google.com"}], "statistics": {"rowCount": 1, "executionTimeMs": 0} } ``` ## Query endpoint (GET) For quick queries and browser testing — pass the Cypher string as a query parameter. | | | |---|---| | Method | `GET` | | URL | `/api/query?q=CYPHER_STRING` | | Headers | `X-API-Key: $API_KEY` | ```bash curl -s -H "X-API-Key: $API_KEY" \ "$BASE/api/query?q=RETURN%201%20AS%20x" ``` ```json { "columns": ["x"], "rows": [{"x": 1}], "statistics": {"rowCount": 1, "executionTimeMs": 0} } ``` Use POST for queries that contain special characters or are longer than a few hundred characters. ## Quota endpoint Run inside `/api/query` to see the active plan, usage, and per-request caps: ```bash curl -s -X POST $BASE/api/query \ -H "Content-Type: application/json" \ -H "X-API-Key: $API_KEY" \ -d '{"query": "CALL whisper.quota()"}' ``` The response is a `(key, value)` table with the following keys: `plan`, `isAnonymous`, `hourlyLimit`/`hourlyUsed`/`hourlyRemaining`/`hourlyResetAt`, `dailyLimit`/`dailyUsed`/`dailyRemaining`/`dailyResetAt`, `maxQueryDepth`, `defaultTimeoutMs`/`maxTimeoutMs`, `defaultResponseLimit`/`maxResponseLimit`, `maxConcurrentQueries`, `concurrentActive`. Numeric limits are returned as integers; `unlimited` is returned as the string `"unlimited"`. ## Stats endpoint Returns aggregate graph statistics with physical, virtual, and total breakdowns plus threat-intelligence status. Results are cached for 60 seconds. | | | |---|---| | Method | `GET` | | URL | `/api/query/stats` | | Headers | `X-API-Key: $API_KEY` | ```bash curl -s -H "X-API-Key: $API_KEY" $BASE/api/query/stats ``` Response: ```json { "physical": { "nodeCount": 3713675717, "edgeCount": 31077961609 }, "virtual": { "nodeCount": 3682770909, "edgeCount": 7978704429 }, "total": { "nodeCount": 7396446626, "edgeCount": 39056666038 }, "objectCount": 46453112664, "threatIntel": { "threatIntelLoaded": true, "hasTaxonomy": true, "feedSourceCount": 39, "categoryCount": 18, "totalListedInEdges": 5281760, "asnEnrichmentLoaded": true, "prefixBgpEnrichmentLoaded": true, "available": true }, "timestamp": "2026-05-14T16:27:39.922374780Z" } ``` Physical counts are nodes and edges stored on disk. Virtual counts are computed at query time from live infrastructure and threat-intelligence data. `objectCount` is the sum of all nodes and edges. The exact numbers grow over time; the snapshot above is from May 2026. ## Threat-intelligence status Returns details about the loaded threat-intelligence layer. | | | |---|---| | Method | `GET` | | URL | `/api/graph/threat-intel/status` | ```json { "refreshInProgress": false, "lastStatus": null, "lastStatusTime": null, "hasTaxonomy": true, "feedSourceCount": 39, "categoryCount": 18, "totalEdges": 5281760, "ipCount": 2507330, "domainCount": 2774430, "timestamp": "2026-05-14T16:53:19.427113540Z" } ``` `refreshInProgress` is `true` while the periodic threat-intel refresh is running; `lastStatus` and `lastStatusTime` reflect the outcome of the previous refresh. The catalog of feeds and categories is documented separately — see [Feed Catalog](https://www.whisper.security/docs/reference/feed-catalog.md) and [Feed Categories](https://www.whisper.security/docs/reference/feed-categories.md). ## Infrastructure status Returns the state of the live infrastructure layer (BGP, RIR allocations, peering). | | | |---|---| | Method | `GET` | | URL | `/api/graph/infrastructure/status` | ```json { "refreshInProgress": false, "lastStatus": null, "lastStatusTime": null, "ready": true, "asnCount": 116467, "registeredPrefixCount": 325660, "announcedPrefixCount": 1398056, "peeringEdgeCount": 457132, "timestamp": "2026-05-14T16:47:22.196777052Z" } ``` `ready` flips to `false` while a refresh is rebuilding the infrastructure layer; queries against affected fields may return `null` until `ready` is back to `true`. ## Error responses Error responses follow [RFC 7807](https://tools.ietf.org/html/rfc7807) problem-detail format. Every error response is a JSON object with at least `type`, `title`, `status`, `detail`, and `timestamp`; specific error types add extra context fields. ```json { "type": "https://whisper.security/errors/query-error", "title": "Query Error", "status": 400, "detail": "Expected RPAREN but got IDENT 'DEFINITELY_INVALID' at position 9", "timestamp": "2026-05-04T18:28:03.951560972Z" } ``` Depth-exceeded errors include both the actual and allowed depth so clients can downgrade automatically: ```json { "type": "https://whisper.security/errors/query-depth-exceeded", "title": "Query Depth Exceeded", "status": 400, "detail": "Query depth 6 exceeds maximum allowed depth of 2. Reduce traversal hops or upgrade your plan for deeper queries.", "actualDepth": 6, "maxAllowedDepth": 2, "timestamp": "2026-05-04T18:28:36.731613220Z" } ``` For the full table of HTTP status codes, rate-limit semantics, and the rate-limit response headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`), see the [Error Codes & Rate Limits reference](https://www.whisper.security/docs/reference/error-codes.md). --- --- ### SOC Analysts & Incident Responders Markdown: https://www.whisper.security/docs/recipes/soc.md HTML: https://www.whisper.security/docs/recipes/soc 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. ```cypher // 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**: ```json [{"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. ```cypher // 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**: ```json [ {"ip.name": "185.220.101.1", "f.name": "Dan Tor Exit"}, {"ip.name": "185.220.101.1", "f.name": "Tor Exit Nodes"}, {"ip.name": "185.220.101.1", "f.name": "IPsum"} ] ``` > **Tip**: Feed listings change daily, so the exact feeds returned will vary. 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. ```cypher // Composite threat score and explanation CALL explain("185.220.101.1") ``` **Sample output**: ```json [{ "indicator": "185.220.101.1", "type": "ip", "found": true, "score": 4.95, "level": "INFO", "explanation": "185.220.101.1 is listed in 3 threat feed(s). Score 5.0 (Informational - minimal risk).", "factors": [ "Listed in 3 source(s) with combined weight 2.20", "Base score: 2.20 × log₂(3 + 1) = 4.40", "Recency boost: ×1.1 (last seen 1 day ago)", "Age boost: ×1.02 (on lists for 1 day)", "Final score: 4.40 × 1.1 × 1.02 = 4.95" ], "sources": [ {"feedId": "dan-tor-exit", "weight": 0.5, "firstSeen": "2026-05-11T01:17:45Z", "lastSeen": "2026-05-12T20:24:15Z"}, {"feedId": "tor-exit-nodes", "weight": 0.5, "firstSeen": "2026-05-11T01:17:44Z", "lastSeen": "2026-05-13T00:29:06Z"}, {"feedId": "stamparm-ipsum", "weight": 1.2, "firstSeen": "2026-05-11T01:17:47Z", "lastSeen": "2026-05-12T22:11:13Z"} ] }] ``` > **Tip**: `CALL explain()` works on IPs, domains, ASNs (`AS13335`), and CIDR ranges (`185.220.101.0/24`). The `sources` array breaks down every feed that contributed, with its weight and first/last-seen timestamps. 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. ```cypher // 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**: ```json [ {"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. ```cypher // 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**: ```json [{"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. ### Neighborhood Toxicity (threat density per prefix) You want to know how many threat-listed IPs share a network prefix with the IP you're investigating. Use the precomputed `threatNeighborCount` virtual property — single 1-hop traversal, ~20ms even for hyperscaler /12 prefixes. ```cypher // Toxic neighbor count for an IP's prefix MATCH (ip:IPV4 {name: "3.69.87.14"})-[:BELONGS_TO]->(p:PREFIX) RETURN p.name AS prefix, p.threatNeighborCount AS toxic_neighbors LIMIT 1 ``` **Sample output**: ```json [{"prefix": "3.64.0.0/12", "toxic_neighbors": 3363}] ``` For the total neighborhood size (denominator for a toxicity ratio), pair with a chained count query — also fast via the engine's degree-count path: ```cypher // Total IPs in the same prefix MATCH (ip:IPV4 {name: "3.69.87.14"})-[:BELONGS_TO]->(p:PREFIX)<-[:BELONGS_TO]-(o:IPV4) RETURN count(o) AS total_neighbors ``` **Sample output**: ```json [{"total_neighbors": 987361}] ``` 3363 / 987361 → 0.34% threat density for that AWS /12. > **Tip**: Avoid the older pattern `MATCH ... <-[:BELONGS_TO]-(o:IPV4) WHERE o.isThreat = true RETURN count(o)`. It enumerates every IP in the prefix (up to ~1M for hyperscaler blocks) and times out at 60s. The precomputed `threatNeighborCount` is built at startup against the LPM-bound announced prefix and refreshed every ~60min. > **Edge case**: `threatNeighborCount` is null for /32 PREFIX nodes (single-IP allocations like Cloudflare and Google DNS). It also includes the queried IP itself if `ip.isThreat = true` — subtract 1 in your rendering when you need a strict "other IPs only" figure. ### Look Up GeoIP Location You need to know the physical location of an IP for a geo-restriction check or incident report. ```cypher // 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**: ```json [{"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. ```cypher // 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**: ```json [{"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. ```cypher // 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**: ```json [{ "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. ```cypher // Authoritative nameservers for a domain MATCH (ns:HOSTNAME)-[:NAMESERVER_FOR]->(h:HOSTNAME {name: "google.com"}) RETURN ns.name LIMIT 10 ``` **Sample output**: ```json [ {"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. ```cypher // 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**: ```json [ {"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. ```cypher // 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**: ```json [ {"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. To score each one, chain `CALL explain(ip) YIELD indicator, score, level` straight after the `UNWIND` — the procedure runs once per row. Each call is a separate backend request, so keep the list modest when chaining `explain()`. --- ## Splunk equivalents If you're enriching events inline rather than running ad-hoc Cypher, see the same workflows in SPL: [Splunk Use Cases for Infrastructure Intel](https://www.whisper.security/docs/integrations/splunk/use-cases.md). For `whisperlookup` and `whisperquery` see [Search Commands](https://www.whisper.security/docs/integrations/splunk/search-commands.md). --- ### Feed Catalog Markdown: https://www.whisper.security/docs/reference/feed-catalog.md HTML: https://www.whisper.security/docs/reference/feed-catalog WhisperGraph aggregates **39 threat intelligence feeds** across **18 categories**. Indicators (IPs, domains, hostnames) appear in the graph as `LISTED_IN` virtual edges to `FEED_SOURCE` nodes, and each `FEED_SOURCE` is classified under a category via the `BELONGS_TO` edge. For category groupings see [Feed Categories](https://www.whisper.security/docs/reference/feed-categories.md). For the scoring algorithm see [`explain()`](https://www.whisper.security/docs/cypher-functions#features). --- ## Live feeds | Feed | Category | |------|----------| | AlienVault Reputation | Reputation | | Binary Defense Banlist | General Blacklists | | Blocklist.de All | General Blacklists | | Blocklist.de Mail | Spam | | Blocklist.de SSH | Brute Force | | Botvrij Domains | Malicious Domains | | Botvrij Dst IPs | C2 Servers | | Brute Force Blocker | Brute Force | | C2 Intel 30d | C2 Servers | | CERT.pl Domains | Malicious Domains | | CINS Score | General Blacklists | | Cloudflare Radar Top 1M | Popularity/Trust | | DNS RD Abuse | General Blacklists | | Dan Tor Exit | TOR Network | | ET Compromised IPs | General Blacklists | | Feodo Tracker | C2 Servers | | FireHOL Abusers 1d | General Blacklists | | FireHOL Anonymous | Proxies | | FireHOL Level 1 | General Blacklists | | FireHOL Level 2 | General Blacklists | | FireHOL Level 3 | General Blacklists | | FireHOL WebClient | General Blacklists | | GreenSnow Blacklist | General Blacklists | | Hagezi Light | Ad/Tracking Blocklists | | Hagezi Pro | Ad/Tracking Blocklists | | IPsum | General Blacklists | | InterServer RBL | General Blacklists | | MalwareBazaar Recent | Malware Distribution | | OpenPhish Feed | Phishing | | SSH Client Attacks | Brute Force | | SSH Password Auth | Brute Force | | SSL IP Blacklist | General Blacklists | | Spamhaus DROP | General Blacklists | | Spamhaus EDROP | General Blacklists | | StevenBlack Hosts | Ad/Tracking Blocklists | | ThreatFox IOCs | C2 Servers | | Tor Exit Nodes | TOR Network | | Tranco Top 1M | Popularity/Trust | | URLhaus Recent | Malware Distribution | The feed set evolves as sources are added or rebalanced — `MATCH (f:FEED_SOURCE) RETURN f.name ORDER BY f.name` returns the current list, and `GET /api/query/stats` reports the live `feedSourceCount`. --- ## Refresh cadence Feeds are aggregated by [whisper-feeds](https://whisper.security), a companion service that pulls from public and commercial sources. Refresh policy: - **Hourly:** incremental refresh — newly listed indicators flow into the graph within ~60 minutes of source publication. - **Daily:** full refresh — every feed is re-fetched and entries no longer present are removed from the graph. This means a hot indicator (e.g. a C2 IP added to ThreatFox) is queryable through `MATCH (ip:IPV4 {name: "..."})-[:LISTED_IN]->(f:FEED_SOURCE)` within an hour. --- ## Querying ```cypher // All feeds an indicator is listed in MATCH (ip:IPV4 {name: "185.220.101.1"})-[:LISTED_IN]->(f:FEED_SOURCE) RETURN f.name ``` ```cypher // All feeds in a category — FEED_SOURCE belongs to CATEGORY via BELONGS_TO MATCH (cat:CATEGORY {name: "C2 Servers"})<-[:BELONGS_TO]-(f:FEED_SOURCE) RETURN f.name ``` ```cypher // The categories a given feed is classified under MATCH (f:FEED_SOURCE {name: "Feodo Tracker"})-[:BELONGS_TO]->(cat:CATEGORY) RETURN cat.name ``` For scored threat assessment, use [`explain()`](https://www.whisper.security/docs/cypher-functions#features) — it factors feed count, weights, recency, and network density into a single score. --- ### Getting Started Markdown: https://www.whisper.security/docs/getting-started.md HTML: https://www.whisper.security/docs/getting-started Run your first WhisperGraph query in five minutes. No SDK required — anything that can POST JSON works. By the end of this page you'll have: - Created an API key - Resolved a hostname to its IP, ASN, and network owner - Pulled a threat-intel score for an indicator - Picked your next path: Cookbook recipes, MCP setup, Splunk integration, or the API reference --- ## 1. Get an API key Create a free API key by signing up at [console.whisper.security](https://console.whisper.security/sign-up). For plans, quotas, and rate limits see the [pricing page](https://www.whisper.security/pricing). > Unauthenticated requests are accepted at the lowest tier — useful for quick exploration. For everything beyond a sample query, send a real key. See [Anonymous access](https://www.whisper.security/docs/cypher-api-reference#anonymous-access). --- ## 2. Run your first query (curl) ```bash curl -s -X POST https://graph.whisper.security/api/query \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR-API-KEY" \ -d '{"query": "MATCH (h:HOSTNAME {name: \"google.com\"}) RETURN h.name"}' ``` Response: ```json { "columns": ["h.name"], "rows": [{"h.name": "google.com"}], "statistics": {"rowCount": 1, "executionTimeMs": 0} } ``` `executionTimeMs` is server-side processing only. Add network latency on top. --- ## 3. The three-step tutorial These three queries take you from a raw indicator to a full infrastructure picture, and from there to a threat assessment. ### Step 1 — Resolve ```cypher MATCH (h:HOSTNAME {name: "www.cloudflare.com"})-[:RESOLVES_TO]->(ip:IPV4) RETURN h.name, ip.name LIMIT 5 ``` ```json { "columns": ["h.name", "ip.name"], "rows": [ {"h.name": "www.cloudflare.com", "ip.name": "104.16.123.96"}, {"h.name": "www.cloudflare.com", "ip.name": "104.16.124.96"} ], "statistics": {"rowCount": 2, "executionTimeMs": 3} } ``` You now know what IPs the hostname resolves to. Always anchor with `{name: "..."}` — unanchored matches scan billions of nodes and time out. ### Step 2 — Enrich ```cypher MATCH (ip:IPV4 {name: "104.16.123.96"}) -[: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 ``` ```json { "columns": ["ip", "prefix", "asn", "network"], "rows": [ { "ip": "104.16.123.96", "prefix": "104.16.112.0/20", "asn": "AS13335", "network": "CLOUDFLARENET - Cloudflare, Inc." } ], "statistics": {"rowCount": 1, "executionTimeMs": 5} } ``` You now know who owns the network behind the IP. The chain `(IPV4)→(ANNOUNCED_PREFIX)→(ASN)→(ASN_NAME)` is the canonical "what's behind this IP" pattern. Live BGP changes can shift which prefix announces the IP, so the exact `/20` you see may differ. ### Step 3 — Explain ```cypher CALL explain("185.220.101.1") ``` ```json { "columns": [ "indicator", "type", "available", "cached", "found", "score", "level", "explanation", "factors", "sources" ], "rows": [{ "indicator": "185.220.101.1", "type": "ip", "available": true, "cached": false, "found": true, "score": 9.2, "level": "INFO", "explanation": "185.220.101.1 is listed in 4 threat feed(s). Score 9.2 (Informational - minimal risk).", "factors": [ "Listed in 4 source(s) with combined weight 3.70", "Base score: 3.70 × log₂(4 + 1) = 8.59", "Age boost: ×1.07 (on lists for 7 days)", "Final score: 8.59 × 1.0 × 1.07 = 9.20" ], "sources": [ {"feedId": "tor-exit-nodes", "weight": 0.5, "firstSeen": "...", "lastSeen": "..."}, {"feedId": "dan-tor-exit", "weight": 0.5, "firstSeen": "...", "lastSeen": "..."}, {"feedId": "stamparm-ipsum", "weight": 1.2, "firstSeen": "...", "lastSeen": "..."}, {"feedId": "firehol-abusers-1d", "weight": 1.5, "firstSeen": "...", "lastSeen": "..."} ] }], "statistics": {"rowCount": 1, "executionTimeMs": 9} } ``` `185.220.101.1` is a public Tor exit node — listed in four threat feeds with a composite score of 9.2. The `factors` array shows how the score was computed: feed count, weights, and a recency boost. `level: INFO` reflects that Tor traffic is anonymising, not inherently malicious. `explain()` works on IPs, domains, ASNs, CIDR ranges, and file hashes. Indicators with no listings return `found: true` with `score: 0.0`, `level: NONE`, and `explanation: "Not listed in any threat intelligence feed"`. See [Cypher Functions & Procedures](https://www.whisper.security/docs/cypher-functions.md) for the full procedure list. --- ## 4. Use a client library ### Python (with `requests`) WhisperGraph doesn't ship an official Python SDK yet (on the roadmap — community contributions welcome). For now, hand-roll with `requests`: ```python import os import requests API_KEY = os.environ["WHISPER_API_KEY"] ENDPOINT = "https://graph.whisper.security/api/query" def query(cypher: str, parameters: dict | None = None) -> list[dict]: """Run a Cypher query and return the rows.""" headers = { "Content-Type": "application/json", "X-API-Key": API_KEY, } body = {"query": cypher} if parameters: body["parameters"] = parameters r = requests.post(ENDPOINT, headers=headers, json=body, timeout=30) r.raise_for_status() return r.json()["rows"] ## Example: resolve, enrich, explain in one go hostname = "www.cloudflare.com" ips = query("MATCH (h:HOSTNAME {name: $name})-[:RESOLVES_TO]->(ip:IPV4) RETURN ip.name LIMIT 5", {"name": hostname}) for row in ips: explanation = query("CALL explain($indicator)", {"indicator": row["ip.name"]}) print(row["ip.name"], "→", explanation[0]["score"], explanation[0]["level"]) ``` ### JavaScript / TypeScript (with `fetch`) ```typescript const API_KEY = process.env.WHISPER_API_KEY!; async function query(cypher: string, parameters?: Record) { const r = await fetch("https://graph.whisper.security/api/query", { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": API_KEY, }, body: JSON.stringify({ query: cypher, parameters }), }); if (!r.ok) throw new Error(`${r.status} ${await r.text()}`); const data = await r.json(); return data.rows as Record[]; } const ips = await query( "MATCH (h:HOSTNAME {name: $name})-[:RESOLVES_TO]->(ip:IPV4) RETURN ip.name LIMIT 5", { name: "www.cloudflare.com" }, ); ``` --- ## What's next You've got the basics. Pick your path. | Goal | Where to go | |------|-------------| | Copy-paste recipes by security workflow | [Cypher Cookbook](https://www.whisper.security/docs/cypher-query-cookbook.md) — by persona | | Wire up an AI assistant (Claude, Cursor, …) | [MCP Client Setup](https://www.whisper.security/docs/mcp/setup.md) | | Enrich Splunk events with WhisperGraph | [Splunk Integration](https://www.whisper.security/docs/integrations/splunk/overview.md) | | Learn the language | [Cypher Syntax](https://www.whisper.security/docs/cypher-syntax.md) and [Best Practices](https://www.whisper.security/docs/cypher-best-practices.md) | | Look up a node label or edge type | [Graph Schema](https://www.whisper.security/docs/reference/graph-schema.md) | | API endpoints and parameters | [Cypher API Reference](https://www.whisper.security/docs/cypher-api-reference.md) | Stuck? See the [FAQ](https://www.whisper.security/docs/faq.md), [Glossary](https://www.whisper.security/docs/reference/glossary.md), or [How to ask for help](https://www.whisper.security/docs/support.md). --- ### Known Limitations Markdown: https://www.whisper.security/docs/known-limitations.md HTML: https://www.whisper.security/docs/known-limitations What WhisperGraph **doesn't** know — published explicitly so you can plan around the gaps. Updated as the graph and ingestion pipelines evolve. --- ## DNS **Subdomain WHOIS history** — WHOIS history is captured per registrable domain. `whisper.history("www.cloudflare.com")` returns nothing because WHOIS doesn't apply to subdomains. Use the parent domain. **Recursive DNS observation gaps** — Observations come from passive DNS, public resolvers, and our own probes. Domains that resolve only via private DNS (split-horizon, internal AD) are not observable. `RESOLVES_TO` will be empty for these. **Stale CNAME chains** — CNAME chains are observed at resolution time. If a target domain is deleted, the chain may persist in the graph for up to 24 hours before being marked stale. --- ## GeoIP **Anycast IPs** — One IP, many physical locations. Cloudflare 1.1.1.1, Google DNS 8.8.8.8, etc. GeoIP for these returns the publisher's nominal HQ, not the actual edge a user hits. Don't use GeoIP for anycast IPs to draw geographic conclusions. **Mobile carrier NAT** — Carrier-grade NAT pools span continents. GeoIP for a mobile IP is a guess. **VPN exit nodes** — GeoIP returns the exit location, not the user's true location. --- ## BGP **Route flux** — A prefix may be announced and withdrawn many times per hour during instability. The graph stores the *current* announcement; historical hops require `whisper.history()`. **MOAS conflicts** — Real hijacks vs legitimate anycast both produce MOAS. WhisperGraph reports the conflict via the `CONFLICTS_WITH` edge; interpretation requires context (RPKI status, ASN reputation, history). **Inferred peering** — `PEERS_WITH` edges are inferred from BGP path observations, not from confirmed peering agreements. They're directionally correct but the metadata (peering type, capacity) isn't ground truth. --- ## Threat intelligence **False positives** — Low-quality feeds occasionally list legitimate infrastructure. Filter by [feed category](https://www.whisper.security/docs/reference/feed-categories.md) or use `explain()` which weights feeds by reliability. **No malware sample data** — File hashes appear in some feeds but the graph does not store the actual samples. Use external sandboxes. **Live backend dependency** — `explain()` and `whisper.history()` fetch from the whisper-feeds service. If it is briefly unavailable they return `available: false` with a `retryAfter` value rather than a score — respect the retry interval. --- ## API and Cypher **Read-only** — No `CREATE`, `MERGE`, `SET`, `DELETE`. See [FAQ](https://www.whisper.security/docs/faq#can-i-write-to-the-graph). **No procedures for write operations** — All procedures (`explain`, `whisper.variants`, `whisper.history`, `whisper.quota`, and the `db.*` introspection calls) are read-only. **Plan-tier depth limits** — Anonymous requests are capped at 2 hops; higher tiers go deeper. For plan limits see the [pricing page](https://www.whisper.security/pricing). **Regex is supported but slow** — `=~` regex matching works (full-match semantics, maximum pattern length 1000 characters), but on a billion-node label like HOSTNAME it falls back to a scan. Prefer `STARTS WITH`, `ENDS WITH`, or `CONTAINS` — all three are index-backed — and reserve regex for small, already-filtered result sets. **Variable-length traversals don't follow virtual edges** — A pattern like `[:ANNOUNCED_BY*1..3]` will not work: `ANNOUNCED_BY`, `LISTED_IN`, `ROUTES` into `ANNOUNCED_PREFIX`, and `BELONGS_TO` into `REGISTERED_PREFIX` are synthesized at query time, and variable-length expansion only walks stored edges. Use fixed-length hops for those. **Global edge-count queries time out** — A query that counts every edge in the graph (`MATCH ()-[r]->() RETURN count(r)`) will not finish. Use `GET /api/query/stats` for global node and edge counts — it answers instantly. **`shortestPath` needs a bounded length** — Write `shortestPath((a)-[*1..6]-(b))`, not an unbounded search. Keep the upper bound modest; a high bound widens the search and slows the query. **`UNWIND` into `CALL explain()` is one backend call per item** — Chaining `UNWIND [...] AS x CALL explain(x) YIELD ...` is supported and is the right way to score a list of indicators, but each item is a separate call to the threat-intelligence backend — keep the list short and expect the running time to grow with it. --- ### Cypher Language Reference Markdown: https://www.whisper.security/docs/cypher-query-guide.md HTML: https://www.whisper.security/docs/cypher-query-guide Welcome to the Cypher language reference for WhisperGraph. The reference is split into focused pages so you can jump to exactly what you need. --- ## Reference pages - [Graph Schema Reference](https://www.whisper.security/docs/reference/graph-schema.md) — Node labels, edge types, multi-hop patterns, and the ER diagram for WhisperGraph. Used by every Cypher query. - [Cypher Syntax Reference](https://www.whisper.security/docs/cypher-syntax.md) — MATCH, WHERE, WITH, RETURN, ORDER BY, aggregations, and path patterns — the Cypher language as supported by WhisperGraph. - [Cypher Functions & Procedures](https://www.whisper.security/docs/cypher-functions.md) — Built-in functions, threat-intel procedures, schema introspection, and other features beyond core Cypher syntax. - [Cypher Best Practices](https://www.whisper.security/docs/cypher-best-practices.md) — Performance rules, common pitfalls, and the do-this / not-that quick-reference table for Cypher on WhisperGraph. --- ## See also - [Cypher API Reference](https://www.whisper.security/docs/cypher-api-reference.md) — endpoints, parameters, errors, rate limits - [Cypher Cookbook](https://www.whisper.security/docs/cypher-query-cookbook.md) — copy-paste recipes by security workflow - [Cypher Cheat Sheet](https://www.whisper.security/docs/cheat-sheet.md) — one-page summary --- ### Threat Intelligence Analysts Markdown: https://www.whisper.security/docs/recipes/threat-intel.md HTML: https://www.whisper.security/docs/recipes/threat-intel 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. ```cypher // 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. ```cypher // 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**: ```json [ {"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. ```cypher // 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**: ```json [ {"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. ```cypher // All domains using a specific nameserver MATCH (ns:HOSTNAME {name: "ns1.google.com"}) -[:NAMESERVER_FOR]->(h:HOSTNAME) RETURN h.name LIMIT 20 ``` **Sample output**: ```json [ {"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. ```cypher // Threat reputation of an entire ASN CALL explain("AS60729") ``` **Sample output**: ```json [{ "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. ```cypher // Historical WHOIS snapshots for a domain CALL whisper.history("cloudflare.com") ``` **Sample output** (first 3 of 27 snapshots): ```json [ {"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. ```cypher // Sites that link to this domain MATCH (source:HOSTNAME)-[:LINKS_TO]->(h:HOSTNAME {name: "cloudflare.com"}) RETURN source.name LIMIT 15 ``` **Sample output**: ```json [ {"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. ```cypher // 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**: ```json [ {"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. --- ## Splunk equivalents For inline threat-intel enrichment in SPL, see [Splunk Use Cases for Infrastructure Intel](https://www.whisper.security/docs/integrations/splunk/use-cases.md) and [Enterprise Security Integration](https://www.whisper.security/docs/integrations/splunk/es-integration.md). For the underlying feed catalog see [Feed Catalog](https://www.whisper.security/docs/reference/feed-catalog.md). --- ### Feed Categories Markdown: https://www.whisper.security/docs/reference/feed-categories.md HTML: https://www.whisper.security/docs/reference/feed-categories Threat-intel feeds in WhisperGraph are grouped into **18 categories**. Use the category to filter feeds when scoring or hunting (e.g. only C2 Servers + Malware Distribution for confirmed-bad). For the full feed list see the [Feed Catalog](https://www.whisper.security/docs/reference/feed-catalog.md). --- ## Categories | Category | What it covers | |----------|----------------| | Ad/Tracking Blocklists | Domains used for ads and behavioural tracking | | Anonymization Infrastructure | VPN exit nodes, proxies, anonymizing relays | | Attack Sources | IPs observed scanning or attacking | | Brute Force | SSH/RDP/credential brute-force sources | | C2 Servers | Confirmed command-and-control infrastructure | | General Blacklists | Catch-all reputation lists from operators | | Malicious Domains | Domains involved in malware or attack chains | | Malicious Infrastructure | Hosting providers and ASNs hosting badness | | Malware Distribution | URLs/IPs serving malware payloads | | Phishing | Domains and URLs used for credential theft | | Popularity/Trust | Top-N domain lists used for whitelisting and reputation | | Proxies | HTTP/SOCKS proxy infrastructure | | Reference Data | Public infrastructure datasets (e.g. Bitcoin nodes) | | Reputation | General reputation aggregators | | Spam | Mail spam sources | | TOR Network | Tor relays and exit nodes | | Threat Intelligence | Curated threat-intel from intel providers | | VPNs | Commercial VPN egress IPs | --- ## Querying by category A `FEED_SOURCE` is connected to its `CATEGORY` by the `BELONGS_TO` edge — there is no `IN_CATEGORY` edge. ```cypher // All feeds in a category MATCH (cat:CATEGORY {name: "C2 Servers"})<-[:BELONGS_TO]-(f:FEED_SOURCE) RETURN f.name ``` ```cypher // Which categories a specific feed is classified under MATCH (f:FEED_SOURCE {name: "Feodo Tracker"})-[:BELONGS_TO]->(cat:CATEGORY) RETURN cat.name ``` ```cypher // Categories an indicator falls under. LISTED_IN and BELONGS_TO are both // virtual edges — chaining them needs a WITH in between to materialise the // feed nodes before the second hop. MATCH (ip:IPV4 {name: "185.220.101.1"})-[:LISTED_IN]->(f:FEED_SOURCE) WITH DISTINCT f MATCH (f)-[:BELONGS_TO]->(cat:CATEGORY) RETURN DISTINCT cat.name ``` --- ### Cypher Syntax Reference Markdown: https://www.whisper.security/docs/cypher-syntax.md HTML: https://www.whisper.security/docs/cypher-syntax The Cypher language clauses and operators supported by WhisperGraph. For schema details (labels, edge types, ER diagram) see the [Graph Schema Reference](https://www.whisper.security/docs/reference/graph-schema.md). For functions and procedures see [Functions & Procedures](https://www.whisper.security/docs/cypher-functions.md). ## Language reference ## MATCH Basic pattern matching. Always anchor your starting node with `{name: "value"}` on large labels like HOSTNAME and IPV4. ```cypher MATCH (n:HOSTNAME {name: "www.google.com"}) RETURN n.name ``` ```cypher MATCH (h:HOSTNAME {name: "www.google.com"})-[:RESOLVES_TO]->(ip:IPV4) RETURN h.name, ip.name LIMIT 10 ``` ## OPTIONAL MATCH Returns null for unmatched patterns instead of dropping the row. Use this for WHOIS fields and other sparse data where not every node has every relationship. ```cypher MATCH (h:HOSTNAME {name: "www.google.com"}) OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR) RETURN h.name, r.name ``` This returns one row with `r.name` as null — WHOIS registration is keyed to the registrable domain, not the subdomain — rather than zero rows. ## WHERE clause **Comparison operators:** `=`, `<>`, `<`, `>`, `<=`, `>=` ```cypher MATCH (n:TLD) WHERE n.name <> "com" RETURN n.name LIMIT 3 ``` **String predicates:** ```cypher // Prefix search (fast, uses index) MATCH (n:HOSTNAME) WHERE n.name STARTS WITH "www.googl" RETURN n.name LIMIT 5 // Substring search (fast) MATCH (n:HOSTNAME) WHERE n.name CONTAINS "cloudflare" RETURN n.name LIMIT 5 // Suffix search (fast on HOSTNAME and TLD, slow on other labels) MATCH (n:HOSTNAME) WHERE n.name ENDS WITH ".google.com" RETURN n.name LIMIT 5 ``` **Regular expressions:** ```cypher MATCH (n:HOSTNAME) WHERE n.name =~ "www\\.google\\.com" RETURN n.name LIMIT 1 ``` Regex uses full-match semantics (not substring). Nested quantifiers are rejected for safety. Maximum pattern length is 1000 characters. Prefer `STARTS WITH`, `ENDS WITH`, or `CONTAINS` instead of regex when possible. **Logical operators:** `AND`, `OR`, `NOT`, `XOR` ```cypher WITH 5 AS x WHERE x > 3 AND x < 10 RETURN x ``` **NULL checks:** `IS NULL`, `IS NOT NULL` ```cypher WITH null AS x RETURN x IS NULL AS result ``` **List membership:** `IN` ```cypher MATCH (n:ASN) WHERE n.name IN ["AS13335", "AS15169"] RETURN n.name ``` ## RETURN Projects columns from matched patterns. Supports aliases with `AS` and `DISTINCT`. ```cypher MATCH (h:HOSTNAME {name: "www.google.com"})-[:RESOLVES_TO]->(ip) RETURN DISTINCT ip.name AS address ``` ```cypher MATCH (h:HOSTNAME {name: "www.google.com"})-[:RESOLVES_TO]->(ip) WITH h.name AS host, collect(ip.name) AS ips RETURN host, ips ``` ## WITH Pipes results between query stages. You can aggregate, filter, and reshape data mid-query. ```cypher MATCH (h:HOSTNAME {name: "www.google.com"})-[:RESOLVES_TO]->(ip) WITH h.name AS host, count(ip) AS ipCount RETURN host, ipCount ``` ```cypher 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 ``` ## ORDER BY, LIMIT, SKIP Sort results and paginate. Always use LIMIT on queries against large labels. ```cypher MATCH (n:TLD) RETURN n.name ORDER BY n.name ASC LIMIT 5 ``` ```cypher MATCH (n:COUNTRY) RETURN n.name ORDER BY n.name SKIP 2 LIMIT 3 ``` Both `SKIP N LIMIT M` and `LIMIT M SKIP N` orderings are supported. ## UNWIND Turns a list into individual rows. This is the batch lookup pattern. ```cypher UNWIND ["google.com", "cloudflare.com", "microsoft.com"] AS domain MATCH (h:HOSTNAME {name: domain}) OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR) RETURN domain, collect(DISTINCT r.name) AS registrars ``` You can pass dozens or hundreds of indicators in a single UNWIND list. ## UNION Combines results from multiple MATCH clauses. `UNION` deduplicates; `UNION ALL` keeps duplicates. ```cypher MATCH (n:TLD {name: "com"}) RETURN n.name UNION MATCH (n:TLD {name: "net"}) RETURN n.name ``` ## CALL subqueries Correlated subqueries import variables with WITH. ```cypher MATCH (h:HOSTNAME {name: "www.google.com"}) CALL { WITH h MATCH (h)-[:RESOLVES_TO]->(ip) RETURN count(ip) AS ipCount } RETURN h.name, ipCount ``` CALL is also used to invoke procedures: ```cypher CALL explain("185.220.101.1") ``` ```cypher CALL whisper.history("cloudflare.com") ``` ```cypher CALL whisper.quota() ``` ```cypher CALL db.labels() ``` ```cypher CALL db.relationshipTypes() ``` A `CALL` placed after `UNWIND`, `WITH`, or `MATCH` runs the procedure once per incoming row, so you can score a whole list in one query: ```cypher UNWIND ["1.1.1.1", "8.8.8.8"] AS ip CALL explain(ip) YIELD indicator, score, level RETURN indicator, score, level ``` ## EXPLAIN Shows the query plan without executing the query. ```cypher EXPLAIN MATCH (h:HOSTNAME {name: "www.google.com"})-[:RESOLVES_TO]->(ip) RETURN ip.name ``` ``` ProduceResult([ip.name]) Project([ip.name]) Expand(h-[RESOLVES_TO]->ip) NodeLookup(h={name:'www.google.com'}:HOSTNAME) ``` Use EXPLAIN to verify the engine is using an indexed lookup rather than a label scan. PROFILE returns the same plan plus row counts per operator. ## CASE / WHEN Conditional expressions. ```cypher RETURN CASE WHEN 1 > 0 THEN "positive" ELSE "negative" END AS result ``` ```cypher MATCH (h:HOSTNAME {name: "cloudflare.com"})-[:RESOLVES_TO]->(ip:IPV4) OPTIONAL MATCH (ip)-[:LISTED_IN]->(f:FEED_SOURCE) RETURN ip.name, CASE WHEN f IS NOT NULL THEN "listed" ELSE "clean" END AS status LIMIT 5 ``` Nested CASE expressions are supported. ## shortestPath Finds the minimum-hop path between two nodes. ```cypher MATCH (a:HOSTNAME {name: "cloudflare.com"}), (b:HOSTNAME {name: "google.com"}) MATCH p = shortestPath((a)-[*1..6]-(b)) RETURN length(p) AS hops, [n IN nodes(p) | n.name] AS path ``` Always specify a bounded path length like `[*1..6]`. Unbounded paths may be slow or time out. `allShortestPaths()` is also supported and returns all paths of the shortest length. ## EXISTS subqueries ```cypher MATCH (h:HOSTNAME {name: "www.google.com"}) WHERE EXISTS { (h)-[:RESOLVES_TO]->() } RETURN h.name ``` The function-style `EXISTS(expr)` predicate is also supported. It returns true if the expression is non-null: ```cypher MATCH (h:HOSTNAME {name: "google.com"}) RETURN EXISTS(h.name) AS has_name ``` ## COUNT subqueries ```cypher MATCH (h:HOSTNAME {name: "www.google.com"}) RETURN h.name, COUNT { (h)-[:RESOLVES_TO]->() } AS ipCount ``` Note: COUNT{}, COLLECT{}, and EXISTS{} subqueries cannot be used directly in ORDER BY. Pre-compute in a WITH clause, then sort by the alias. ## COLLECT subqueries ```cypher MATCH (h:HOSTNAME {name: "www.google.com"}) RETURN h.name, COLLECT { MATCH (h)-[:RESOLVES_TO]->(ip) RETURN ip.name } AS ips ``` ## List comprehensions ```cypher RETURN [x IN range(1, 10) WHERE x % 2 = 0] AS evens ``` ```cypher WITH [1, 2, 3, 4, 5] AS nums RETURN [x IN nums WHERE x > 2 | x * 10] AS filtered ``` ## Pattern comprehensions ```cypher MATCH (h:HOSTNAME {name: "www.google.com"}) RETURN h.name, [(h)-[:RESOLVES_TO]->(ip) | ip.name] AS ips ``` --- ### How to Ask for Help Markdown: https://www.whisper.security/docs/support.md HTML: https://www.whisper.security/docs/support Where to go when something isn't working. We answer faster when you include the right context. --- ## Open a support ticket Two ways to reach us: - **Support portal** — open a ticket at [console.whisper.security/support](https://console.whisper.security/support). Best for tracked, async requests. - **Email** — [support@whisper.security](mailto:support@whisper.security). Include: 1. **What you ran.** The full Cypher query (or curl command), or the Splunk SPL. 2. **What you expected.** Briefly. 3. **What you got.** The full response — headers and body. For Splunk, the search command output and any `internal` log entries. 4. **The request ID.** WhisperGraph error responses include a `request_id` field; copy it. 5. **The time.** Approximate UTC time of the failed request. 6. **Your plan tier.** Run `CALL whisper.quota()` and paste the output. For Splunk-specific issues, attach the diag bundle: `/opt/splunk/bin/splunk diag --collect TA-whisper-graph`. See [Splunk Troubleshooting](https://www.whisper.security/docs/integrations/splunk/troubleshooting.md). --- ## Ticket template ``` Subject: [WhisperGraph] Plan tier: Region: Time (UTC): What I ran: What I expected: What I got: request_id: ``` --- ## Where else to look - **[FAQ](https://www.whisper.security/docs/faq.md)** — common questions - **[Glossary](https://www.whisper.security/docs/reference/glossary.md)** — definitions for terms you'll see in error messages - **[Error Codes & Rate Limits](https://www.whisper.security/docs/reference/error-codes.md)** — every documented HTTP status with recovery steps - **[Known Limitations](https://www.whisper.security/docs/known-limitations.md)** — what WhisperGraph doesn't know - **[Splunk Troubleshooting](https://www.whisper.security/docs/integrations/splunk/troubleshooting.md)** — Splunk-specific issues - **[Changelog](https://www.whisper.security/docs/changelog.md)** — recent changes that may affect you --- ## Status and incidents Live status: [status.whisper.security](https://status.whisper.security) For active incidents we post updates within 15 minutes of detection. --- ### Penetration Testers & Red Teams Markdown: https://www.whisper.security/docs/recipes/pentest-recon.md HTML: https://www.whisper.security/docs/recipes/pentest-recon 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. ```cypher // All indexed subdomains of a target MATCH (h:HOSTNAME) WHERE h.name ENDS WITH ".github.com" RETURN h.name LIMIT 30 ``` **Sample output**: ```json [ {"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. ```cypher // 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**: ```json [{"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. ```cypher // All indexed hosts starting with a specific prefix MATCH (h:HOSTNAME) WHERE h.name STARTS WITH "vpn." RETURN h.name LIMIT 15 ``` **Sample output**: ```json [ {"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. ```cypher // 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**: ```json [ {"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. ```cypher // 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. ```cypher // MX records: mail servers for a target domain MATCH (mx:HOSTNAME)-[:MAIL_FOR]->(h:HOSTNAME {name: "cloudflare.com"}) RETURN mx.name ``` **Sample output**: ```json [ {"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. ```cypher // 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**: ```json [{"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. ```cypher // 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**: ```json [{ "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. ```cypher // All IP prefixes announced by an ASN MATCH (a:ASN {name: "AS15169"})-[:ROUTES]->(p) RETURN a.name, p.name LIMIT 20 ``` **Sample output**: ```json [ {"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. --- ## Splunk equivalents For attack-surface monitoring inside Splunk, see [Splunk Use Cases](https://www.whisper.security/docs/integrations/splunk/use-cases.md) and the owned-domain modular input documented in [Modular Inputs](https://www.whisper.security/docs/integrations/splunk/modular-inputs.md). --- ### Error Codes & Rate Limits Markdown: https://www.whisper.security/docs/reference/error-codes.md HTML: https://www.whisper.security/docs/reference/error-codes WhisperGraph error responses follow [RFC 7807](https://tools.ietf.org/html/rfc7807) problem-detail format. Every error response is a JSON object with at least `type`, `title`, `status`, and `detail`. ```json { "type": "https://whisper.security/errors/query-depth-exceeded", "title": "Query Depth Exceeded", "status": 400, "detail": "Query depth 3 exceeds maximum allowed depth of 2. Reduce traversal hops or upgrade your plan for deeper queries.", "actualDepth": 3, "maxAllowedDepth": 2 } ``` --- ## HTTP status codes | Code | Condition | What to do | |------|-----------|------------| | `400` | Invalid Cypher syntax | Check your query for typos or unsupported syntax. See the [Cypher Syntax reference](https://www.whisper.security/docs/cypher-syntax.md). | | `400` | Query depth exceeds plan limit | Reduce traversal hops or upgrade your plan. Anonymous requests are capped at 2 hops; higher tiers allow deeper traversals — see the [pricing page](https://www.whisper.security/pricing). | | `401` | The request was rejected for an authentication reason | Check your `X-API-Key` header. Note that an unknown or malformed key normally degrades to anonymous access rather than returning 401 — if results look thin, see the anonymous-access note in the [API reference](https://www.whisper.security/docs/cypher-api-reference#anonymous-access). | | `408` | Query timeout | Add `LIMIT`, narrow the pattern, or anchor with `{name: "..."}`. The default and maximum per-request timeouts are plan-specific — check them with `CALL whisper.quota()` (`defaultTimeoutMs` / `maxTimeoutMs`). | | `429` | Hourly or daily quota exceeded, or too many concurrent queries | Wait for the reset time in the response headers, or upgrade your plan. | | `500` | Server error | Retry once. If it persists, contact [support](mailto:support@whisper.security) with the request ID. | | `502` | Upstream service error | A downstream graph backend returned an error. Usually transient — retry with exponential backoff. | | `503` | External service unavailable | An infrastructure data source (BGP, WHOIS, threat-intel) is temporarily unreachable. Affected fields may return null, and procedures like `explain()` may return `available: false` with a `retryAfter` value. | | `504` | Gateway timeout | The request timed out at the gateway. Same as 408: anchor the query better, add `LIMIT`, simplify the traversal. | --- ## Rate limits Quota is enforced on an **hourly** and a **daily** window. When you hit a 429, the response includes the rate-limit headers: - `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` — your hourly quota, queries left this hour, and when the hourly window resets. - `X-DailyLimit-Limit` / `X-DailyLimit-Remaining` / `X-DailyLimit-Reset` — the matching values for the daily window. Authenticated responses include these headers on every request, not just 429s — so you can watch your remaining quota without a separate call. Wait until the relevant reset time and retry, or run `CALL whisper.quota()` to see your current usage. For per-plan limits and quotas see the [pricing page](https://www.whisper.security/pricing). --- ## Reporting issues When opening a support ticket, include: 1. The full request URL and request body 2. The full response (headers + body) 3. The `request_id` from the response (if present) 4. The time the request was made (UTC) Open a ticket at [console.whisper.security/support](https://console.whisper.security/support) or email [support@whisper.security](mailto:support@whisper.security). --- ### Cypher Functions & Procedures Markdown: https://www.whisper.security/docs/cypher-functions.md HTML: https://www.whisper.security/docs/cypher-functions Functions, stored procedures, and built-in features. For the underlying Cypher syntax see [Cypher Syntax](https://www.whisper.security/docs/cypher-syntax.md). The full list of threat-intel feed sources lives in the [Feed Catalog](https://www.whisper.security/docs/reference/feed-catalog.md). ## Function reference ## Aggregation | Function | Description | Example | |----------|-------------|---------| | `count(*)` | Count rows | `MATCH (n:TLD) RETURN count(*)` | | `count(DISTINCT x)` | Count unique values | `MATCH (n:COUNTRY) RETURN count(DISTINCT n.name)` | | `collect(x)` | Collect values into a list | `MATCH (n:RIR) RETURN collect(n.name)` | | `sum(x)` | Sum numeric values | `UNWIND [1,2,3] AS x RETURN sum(x)` | | `avg(x)` | Average | `UNWIND [1,2,3] AS x RETURN avg(x)` | | `min(x)` | Minimum | `UNWIND [3,1,4] AS x RETURN min(x)` | | `max(x)` | Maximum | `UNWIND [3,1,4] AS x RETURN max(x)` | | `stdev(x)` | Sample standard deviation | `UNWIND [1,2,3,4,5] AS x RETURN stdev(x)` | | `stdevp(x)` | Population standard deviation | `UNWIND [1,2,3,4,5] AS x RETURN stdevp(x)` | | `percentileDisc(x, p)` | Discrete percentile | `UNWIND [1,2,3,4,5] AS x RETURN percentileDisc(x, 0.5)` | | `percentileCont(x, p)` | Continuous percentile | `UNWIND [1,2,3,4,5] AS x RETURN percentileCont(x, 0.9)` | Note: `min()` and `max()` work on both aggregated rows and list literals. ## String | Function | Example | Result | |----------|---------|--------| | `toUpper("hello")` | | `"HELLO"` | | `toLower("HELLO")` | | `"hello"` | | `trim(" hello ")` | | `"hello"` | | `lTrim(" hello")` | | `"hello"` | | `rTrim("hello ")` | | `"hello"` | | `replace("hello world", "world", "cypher")` | | `"hello cypher"` | | `substring("hello world", 6)` | | `"world"` | | `substring("hello world", 0, 5)` | | `"hello"` | | `split("a.b.c", ".")` | | `["a", "b", "c"]` | | `left("hello", 3)` | | `"hel"` | | `right("hello", 3)` | | `"llo"` | | `reverse("hello")` | | `"olleh"` | | `size("hello")` | | `5` | ## Numeric | Function | Example | Result | |----------|---------|--------| | `abs(-42)` | | `42` | | `ceil(3.14)` | | `4.0` | | `floor(3.99)` | | `3.0` | | `round(3.5)` | | `4` | | `sign(-42)` | | `-1` | | `rand()` | | Random float 0..1 | | `log(e())` | | `1.0` | | `ln(e())` | | `1.0` | | `log10(100)` | | `2.0` | | `exp(1)` | | `2.718...` | | `sqrt(144)` | | `12.0` | | `e()` | | `2.718...` | | `pi()` | | `3.14159...` | **Arithmetic operators:** `+`, `-`, `*`, `/`, `%`, `^` (exponent), `+` (string concatenation). ## Trigonometric `sin()`, `cos()`, `tan()`, `asin()`, `acos()`, `atan()`, `atan2()`, `degrees()`, `radians()` All accept and return radians, except `degrees()` which converts radians to degrees, and `radians()` which converts degrees to radians. ## Geospatial | Function | Description | |----------|-------------| | `point({latitude: 37.77, longitude: -122.42})` | Create a geographic point | | `point.distance(p1, p2)` | Haversine distance in meters between two points | | `distance(p1, p2)` | Alias for `point.distance` | ## Collection | Function | Example | Result | |----------|---------|--------| | `head([1,2,3])` | | `1` | | `last([1,2,3])` | | `3` | | `tail([1,2,3])` | | `[2,3]` | | `size([1,2,3])` | | `3` | | `reverse([1,2,3])` | | `[3,2,1]` | | `range(1, 5)` | | `[1,2,3,4,5]` | | `range(0, 10, 2)` | | `[0,2,4,6,8,10]` | | `keys({a: 1, b: 2})` | | `["a", "b"]` | ## List predicates | Function | Example | Result | |----------|---------|--------| | `all(x IN list WHERE pred)` | `all(x IN [2,4,6] WHERE x % 2 = 0)` | `true` | | `any(x IN list WHERE pred)` | `any(x IN [1,2,3] WHERE x > 2)` | `true` | | `none(x IN list WHERE pred)` | `none(x IN [1,2,3] WHERE x > 5)` | `true` | | `single(x IN list WHERE pred)` | `single(x IN [1,2,3] WHERE x = 2)` | `true` | | `reduce(acc=init, x IN list \| expr)` | `reduce(t=0, x IN [1,2,3,4,5] \| t+x)` | `15` | ## Node and relationship | Function | Description | |----------|-------------| | `labels(n)` | Returns the list of labels on a node, e.g. `["HOSTNAME"]` | | `type(r)` | Returns edge type name, e.g. `"RESOLVES_TO"` | | `id(n)` | Returns node ID | | `elementId(n)` | Returns the node's element ID | | `properties(n)` | Returns all properties as a map | | `startNode(r)` | Source node of a relationship | | `endNode(r)` | Target node of a relationship | | `nodes(p)` | All nodes in a path | | `relationships(p)` | All relationships in a path | | `length(p)` | Number of edges in a path | ## Type conversion | Function | Example | Result | |----------|---------|--------| | `toString(42)` | | `"42"` | | `toInteger("42")` | | `42` | | `toFloat("3.14")` | | `3.14` | | `toBoolean("true")` | | `true` | | `toIntegerList(["1","2"])` | | `[1, 2]` | | `toFloatList(["1.1","2.2"])` | | `[1.1, 2.2]` | | `toStringList([1,2])` | | `["1", "2"]` | | `toBooleanList(["true","false"])` | | `[true, false]` | | `isEmpty("")` | | `true` | | `coalesce(null, null, 42)` | | `42` | | `randomUUID()` | | UUID string | Input that cannot be parsed yields null rather than an error: `toInteger("abc")` returns null. ## Date and time | Function | Description | |----------|-------------| | `datetime()` | Current UTC datetime | | `date()` | Current date | | `time()` | Current time | | `localtime()` | Current local time | | `localdatetime()` | Current local datetime | | `timestamp()` | Current epoch milliseconds | | `datetime("2024-01-15T10:30:00Z")` | Parse an ISO 8601 datetime string | | `duration("P1Y2M3D")` | Create a duration from ISO 8601 | | `duration.between(dt1, dt2)` | Duration between two datetimes | Property access on datetime values: `.year`, `.month`, `.day`, `.hour`, `.minute`, `.second`, `.epochMillis`, `.epochSeconds`. --- ## Procedures Procedures are built-in operations you invoke with `CALL`. WhisperGraph has twelve, in four groups: threat intelligence, brand protection, history and quota, and schema introspection. | Procedure | Arguments | What it returns | |-----------|-----------|-----------------| | `explain(indicator)` | one indicator string | A threat score, level, explanation, factors, and contributing sources | | `whisper.explain(indicator)` | one indicator string | The same as `explain()`, under a longer name | | `whisper.variants(name)` | a domain, plus options | Typosquatting variants of the domain | | `whisper.history(indicator)` | one indicator string | Historical WHOIS or BGP routing snapshots | | `whisper.quota()` | none | Your plan tier and remaining quota | | `db.labels()` | none | Every node label, with row counts | | `db.relationshipTypes()` | none | Every edge type, with row counts and the labels it connects | | `db.propertyKeys()` | none | Every property key in the graph | | `db.schema()` | optional format string | Node labels, edge types, and how they connect | | `db.schema.visualization()` | none | The schema as a Cypher-style text map | | `db.schema.nodeTypeProperties()` | none | One row per label and property | | `db.schema.relTypeProperties()` | none | One row per edge type and its property | A `CALL` can stand alone, or feed the rest of a query with `YIELD`. When it follows `UNWIND`, `WITH`, or `MATCH`, the procedure runs once per incoming row — that is how you score a list of indicators in one query: ```cypher UNWIND ["1.1.1.1", "8.8.8.8"] AS ip CALL explain(ip) YIELD indicator, score, level RETURN indicator, score, level ``` --- ## Features ## Threat intelligence WhisperGraph indexes 39 threat intelligence feeds across 18 categories. This data comes from whisper-feeds, a companion service that aggregates live threat feed data from public and commercial sources, with hourly incremental and daily full refresh cycles. The threat intelligence layer works like this: - IPs and hostnames that appear in threat feeds are connected to FEED_SOURCE nodes via `LISTED_IN` virtual edges. - Each FEED_SOURCE belongs to one or more CATEGORYs (e.g., "C2 Servers", "Phishing", "General Blacklists") via the `BELONGS_TO` edge. - ASN and prefix nodes carry aggregate threat properties such as `threatScore` and `threatLevel`. - The `explain()` procedure computes a composite score that factors in feed count, feed weights, recency, and network density. **Querying threat feeds directly:** ```cypher MATCH (ip:IPV4 {name: "185.220.101.1"})-[:LISTED_IN]->(f:FEED_SOURCE) RETURN ip.name, f.name ``` ```json [ {"ip.name": "185.220.101.1", "f.name": "Dan Tor Exit"}, {"ip.name": "185.220.101.1", "f.name": "Tor Exit Nodes"}, {"ip.name": "185.220.101.1", "f.name": "IPsum"} ] ``` **Scored threat assessment with `explain()`:** `explain()` accepts IPs, domains, ASNs, CIDR ranges, and file hashes. The response carries the indicator `type`, a numeric `score`, a normalized `level`, a human-readable `explanation`, a `factors` array showing how the score was built, and a `sources` array of the contributing feeds. ```cypher CALL explain("185.220.101.1") ``` ```json [{ "indicator": "185.220.101.1", "type": "ip", "available": true, "cached": false, "found": true, "score": 4.95, "level": "INFO", "explanation": "185.220.101.1 is listed in 3 threat feed(s). Score 5.0 (Informational - minimal risk).", "factors": [ "Listed in 3 source(s) with combined weight 2.20", "Base score: 2.20 × log₂(3 + 1) = 4.40", "Recency boost: ×1.1 (last seen 1 day ago)", "Age boost: ×1.02 (on lists for 1 day)", "Final score: 4.40 × 1.1 × 1.02 = 4.95" ], "sources": [ {"feedId": "dan-tor-exit", "weight": 0.5, "firstSeen": "2026-05-11T01:17:45Z", "lastSeen": "2026-05-12T20:24:15Z"}, {"feedId": "tor-exit-nodes", "weight": 0.5, "firstSeen": "2026-05-11T01:17:44Z", "lastSeen": "2026-05-13T00:29:06Z"}, {"feedId": "stamparm-ipsum", "weight": 1.2, "firstSeen": "2026-05-11T01:17:47Z", "lastSeen": "2026-05-12T22:11:13Z"} ] }] ``` ```cypher CALL explain("cloudflare.com") ``` ```json [{ "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)." }] ``` For an ASN, the score reflects the threat density of the network's address space, and the row carries a `breakdown` of composite sub-scores: ```cypher CALL explain("AS60729") ``` ```json [{ "indicator": "AS60729", "type": "asn", "found": true, "explanation": "AS60729 (TORSERVERS-NET, DE) has a reputation score of 51.0 (Suspicious).", "breakdown": {"threatDensityScore": 90, "graphMetricsScore": 40, "historicalScore": 60, "prefixAgeScore": 30} }] ``` For a CIDR range, the score reflects how many of the block's IPs are listed: ```cypher CALL explain("185.220.101.0/24") ``` ```json [{ "indicator": "185.220.101.0/24", "type": "network", "found": true, "level": "MEDIUM", "explanation": "Network 185.220.101.0/24 contains 176 listed IP(s) and 28 listed subnet(s). Threat density: 68.75%. Score 74.7 (Medium risk - investigate further)." }] ``` The `level` field returns one of: `NONE`, `INFO`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`. For automated rules, branch on `level` rather than the raw `score` — the level boundaries are calibrated to human-readable risk language. An indicator with no listings returns `found: true` with `score: 0.0` and `level: NONE`. `explain()` fetches live data from whisper-feeds. If that service is briefly unavailable, the row comes back with `available: false`, an `error` string, and a `retryAfter` value (in seconds) — respect the retry interval rather than retrying in a loop. `whisper.explain()` is the same procedure under a longer name. **Feed sources (all 39):** | Feed | Category | |------|----------| | AlienVault Reputation | Reputation | | Binary Defense Banlist | General Blacklists | | Blocklist.de All | General Blacklists | | Blocklist.de Mail | Spam | | Blocklist.de SSH | Brute Force | | Botvrij Domains | Malicious Domains | | Botvrij Dst IPs | C2 Servers | | Brute Force Blocker | Brute Force | | C2 Intel 30d | C2 Servers | | CERT.pl Domains | Malicious Domains | | CINS Score | General Blacklists | | Cloudflare Radar Top 1M | Popularity/Trust | | DNS RD Abuse | General Blacklists | | Dan Tor Exit | TOR Network | | ET Compromised IPs | General Blacklists | | Feodo Tracker | C2 Servers | | FireHOL Abusers 1d | General Blacklists | | FireHOL Anonymous | Proxies | | FireHOL Level 1 | General Blacklists | | FireHOL Level 2 | General Blacklists | | FireHOL Level 3 | General Blacklists | | FireHOL WebClient | General Blacklists | | GreenSnow Blacklist | General Blacklists | | Hagezi Light | Ad/Tracking Blocklists | | Hagezi Pro | Ad/Tracking Blocklists | | IPsum | General Blacklists | | InterServer RBL | General Blacklists | | MalwareBazaar Recent | Malware Distribution | | OpenPhish Feed | Phishing | | SSH Client Attacks | Brute Force | | SSH Password Auth | Brute Force | | SSL IP Blacklist | General Blacklists | | Spamhaus DROP | General Blacklists | | Spamhaus EDROP | General Blacklists | | StevenBlack Hosts | Ad/Tracking Blocklists | | ThreatFox IOCs | C2 Servers | | Tor Exit Nodes | TOR Network | | Tranco Top 1M | Popularity/Trust | | URLhaus Recent | Malware Distribution | **Categories (all 18):** Ad/Tracking Blocklists, Anonymization Infrastructure, Attack Sources, Brute Force, C2 Servers, General Blacklists, Malicious Domains, Malicious Infrastructure, Malware Distribution, Phishing, Popularity/Trust, Proxies, Reference Data, Reputation, Spam, TOR Network, Threat Intelligence, VPNs. ## Domain variants `whisper.variants()` generates typosquatting variants of a domain and, by default, returns only the ones that are actually registered in the graph. It is built for brand protection and phishing investigation: give it a brand domain, and it tells you which look-alike domains exist. ```cypher CALL whisper.variants("paypal.com") ``` ```json [ {"variant": "aypal.com", "method": "OMISSION", "exists": true, "confidence": 0.7, "confidenceLabel": "medium"}, {"variant": "ppaypal.com", "method": "REPETITION", "exists": true, "confidence": 0.7, "confidenceLabel": "medium"}, {"variant": "apypal.com", "method": "TRANSPOSITION", "exists": true, "confidence": 0.7, "confidenceLabel": "medium"}, {"variant": "oaypal.com", "method": "KEYBOARD_REPLACEMENT", "exists": true, "confidence": 0.8, "confidenceLabel": "high"}, {"variant": "paypal.net", "method": "TLD_SWAP", "exists": true, "confidence": 0.5, "confidenceLabel": "medium"} ] ``` Each result carries the generation `method`, an `exists` flag, and a `confidence` score with a `confidenceLabel` of `low`, `medium`, or `high`. The generation methods include character `OMISSION`, `REPETITION`, `TRANSPOSITION`, `KEYBOARD_REPLACEMENT`, `KEYBOARD_INSERTION`, `VOWEL_SWAP`, `BITSQUATTING`, `HOMOGLYPH`, `HYPHENATION`, `DOT_INSERTION`, `TLD_SWAP`, `TLD_ADDITION`, and `SUBDOMAIN_ADD`. By default the procedure checks the `HOSTNAME` label and returns only registered variants. Pass a label as the second argument to check a different node type, or pass `false` as the filter argument to return every generated variant whether or not it is registered. `exists: true` means the variant is registered, **not** that it is malicious. Run `explain()` on the hits for a threat verdict, then pull their DNS and WHOIS records to see where they point and who registered them. The variant generator chains naturally into a traversal: ```cypher CALL whisper.variants("paypal.com") YIELD variant, method WITH variant, method MATCH (h:HOSTNAME {name: variant})-[:RESOLVES_TO]->(ip:IPV4) RETURN variant, method, ip.name AS resolves_to LIMIT 20 ``` ## Domain and routing history The `whisper.history()` procedure returns timestamped WHOIS and BGP snapshots for an indicator. ```cypher CALL whisper.history("cloudflare.com") ``` ```json [ { "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." } ] ``` Supported indicator types: domain, IP, ASN, and CIDR prefix. For IPs, prefixes, and ASNs it returns BGP routing history (origin changes, announcements, withdrawals) instead of WHOIS. History works on registrable domains (e.g., `google.com`), not subdomains (`www.google.com`). Subdomains return empty results. Like `explain()`, the procedure fetches live data and may return `available: false` with a `retryAfter` value when the upstream service is busy. ## Quota and plan info ```cypher CALL whisper.quota() ``` Returns a `(key, value)` table with your current plan tier, the hourly and daily query limits with usage and reset times, the maximum traversal depth, the default and maximum per-request timeouts, the response-row limit, and the concurrent-query limit. This call does not count against your quota. ## Schema introspection Discover what is in the graph without knowing the schema up front. ```cypher // All node labels with counts CALL db.labels() ``` Returns every label in the graph with its count, including virtual labels (REGISTERED_PREFIX, ANNOUNCED_PREFIX, FEED_SOURCE, CATEGORY). ```cypher // All edge types with counts CALL db.relationshipTypes() ``` Returns every edge type with its count and the labels it connects, including virtual edge types (LISTED_IN, ANNOUNCED_BY, OPERATES, CONFLICTS_WITH). ```cypher // All property keys CALL db.propertyKeys() ``` Returns all known property keys in the graph. ```cypher // Full schema summary CALL db.schema() ``` Returns labels, relationship types, and how they connect. It accepts an optional format string: `db.schema('json')` for machine-readable JSON, `db.schema('markdown')` for tables, or `db.schema('details')` for a verbose breakdown with every property. `db.schema.visualization()` draws the schema as a Cypher-style text map, and `db.schema.nodeTypeProperties()` / `db.schema.relTypeProperties()` list properties one row at a time. These are all instant histogram lookups, regardless of graph size — a cheap way to plan a query before you write the traversal. --- ### Glossary Markdown: https://www.whisper.security/docs/reference/glossary.md HTML: https://www.whisper.security/docs/reference/glossary Definitions for terms used across WhisperGraph documentation. Terms are grouped roughly by domain. Click any term to jump to the doc page where it's used in context. --- ## Network and routing **ASN (Autonomous System Number)** — A unique number identifying a network operator on the public internet. ASNs route IP prefixes via BGP. WhisperGraph stores ASNs as `ASN` nodes connected to prefixes via `ROUTES`. See [SOC recipes](https://www.whisper.security/docs/recipes/soc.md). See also: [Autonomous System (ASN) — full glossary entry](https://www.whisper.security/glossary/autonomous-system.md). **ANNOUNCED_PREFIX** — A CIDR block that an ASN is currently announcing on BGP. `(IPV4)-[:ANNOUNCED_BY]->(ANNOUNCED_PREFIX)-[:ROUTES]->(ASN)` is the current routing chain. **REGISTERED_PREFIX** — A CIDR block allocated to an organization by a [RIR](#rir-regional-internet-registry). Static; doesn't change as the network re-routes. **BGP (Border Gateway Protocol)** — The internet's path-vector routing protocol. Operators announce prefixes; ASNs choose paths. See also: [BGP Routing — full glossary entry](https://www.whisper.security/glossary/bgp-routing.md). **BGP hijack** — When an ASN announces a prefix it doesn't own, redirecting traffic. WhisperGraph detects [MOAS conflicts](#moas-conflict) — see [BGP recipes](https://www.whisper.security/docs/recipes/bgp-routing.md). **MOAS conflict** — *Multiple Origin AS*: two different ASNs announcing the same prefix. WhisperGraph surfaces these as the `CONFLICTS_WITH` edge (`PREFIX → ASN`). Often a sign of hijack; sometimes legitimate (e.g. anycast). **RPKI (Resource Public Key Infrastructure)** — Cryptographic system that validates BGP announcements. WhisperGraph tracks RPKI status on prefixes — `isRpkiValid` and `rpkiStatus` properties. **RIR (Regional Internet Registry)** — One of five regional bodies (ARIN, RIPE, APNIC, LACNIC, AFRINIC) that allocate IP and ASN resources. **Anycast** — One IP announced from many physical locations. CDN networks (Cloudflare, Fastly) use it. GeoIP results for anycast IPs are unreliable — see [Known Limitations](https://www.whisper.security/docs/known-limitations.md). --- ## DNS and email **A record / AAAA record** — DNS record mapping a hostname to an IPv4 / IPv6. `(:HOSTNAME)-[:RESOLVES_TO]->(:IPV4|:IPV6)` in the graph. See also: [DNS — full glossary entry](https://www.whisper.security/glossary/dns.md). **CNAME** — DNS alias from one hostname to another. Often chained 2–3 hops deep. **MX record** — Mail exchanger record. Points a domain at the host that accepts its email. See also: [DNS — full glossary entry](https://www.whisper.security/glossary/dns.md). **SPF (Sender Policy Framework)** — DNS TXT record listing IPs allowed to send mail for a domain. Often includes other SPF records via `include:` directives. **SPF include chain** — The transitive set of all SPF records reachable from a starting domain's `include:` directives. WhisperGraph traces these — see [DNS/email recipes](https://www.whisper.security/docs/recipes/dns-email.md). **DNSSEC** — Cryptographic authentication of DNS responses. WhisperGraph stores the signing algorithm via the `SIGNED_WITH` edge. See also: [DNSSEC — full glossary entry](https://www.whisper.security/glossary/dnssec.md). **Dangling DNS record** — A DNS record pointing to infrastructure the owner no longer controls (e.g. a deleted S3 bucket). Vector for subdomain takeover. --- ## Threat intel **FEED_SOURCE** — A graph node representing one threat-intel feed (e.g. "ThreatFox IOCs"). Indicators connect via `LISTED_IN`; the feed is classified under a category via `BELONGS_TO`. See [Feed Catalog](https://www.whisper.security/docs/reference/feed-catalog.md). **CATEGORY** — A grouping of feed sources (e.g. "C2 Servers", "Phishing"). 18 total. See [Feed Categories](https://www.whisper.security/docs/reference/feed-categories.md). **LISTED_IN** — Virtual edge connecting an indicator (IP, hostname, domain) to a FEED_SOURCE. **explain()** — Stored procedure that scores an indicator using feed count, weights, recency, and network density. Returns a composite score, a normalized `level`, and a human-readable explanation. Works on IPs, domains, ASNs, CIDR ranges, and file hashes. See [Cypher Functions & Procedures](https://www.whisper.security/docs/cypher-functions.md). **Threat score** — The composite numeric score returned by `explain()`. Higher means more confidently bad; the value is a weighted composite, not a fixed 0–10 scale (a heavily-listed network can score well into the dozens). Computed from listed feeds, feed weight, recency boost, and network neighborhood density. For automated rules, branch on the `level` tier (`NONE` … `CRITICAL`) rather than the raw score. --- ## Graph mechanics **Node label** — The "type" of a node, e.g. `HOSTNAME`, `IPV4`, `ASN`. See [Graph Schema](https://www.whisper.security/docs/reference/graph-schema.md). **Edge type** — The "type" of a relationship between nodes, e.g. `RESOLVES_TO`, `ANNOUNCED_BY`, `LISTED_IN`. **Physical edge** — An edge backed by an actual stored row (e.g. `RESOLVES_TO` from a DNS observation). Traversable with full predicate support. **Virtual edge** — An edge computed at query time from indexed properties (e.g. `LISTED_IN`, `ANNOUNCED_BY`, `OPERATES`). Traversable like any other edge, but the source node must be anchored, and a virtual edge cannot be used inside a variable-length pattern. **Anchor** — The starting node of a traversal, identified by an indexed property like `{name: "..."}`. **Always anchor** — unanchored `MATCH (n:LABEL)` scans every node of that label. **Multi-hop traversal** — A pattern with multiple edges, e.g. `(a)-[:E1]->(b)-[:E2]->(c)`. Plan tier sets the maximum hop count — anonymous requests are capped at 2; higher tiers go deeper (see the [pricing page](https://www.whisper.security/pricing)). See [Error Codes](https://www.whisper.security/docs/reference/error-codes#http-status-codes). --- ## Splunk integration **TA (Technology Add-on)** — A Splunk add-on package. WhisperGraph ships TA-whisper-graph. **SPL (Search Processing Language)** — Splunk's query language. WhisperGraph adds custom commands like `whisperlookup` and `whisperquery`. **KV Store** — Splunk's key-value store. Used by WhisperGraph for enrichment caching and threat-intel populators. **SHC (Search Head Cluster)** — A clustered Splunk search-head deployment. The WhisperGraph TA supports SHC bundle deployments. **RBA (Risk-Based Alerting)** — Splunk Enterprise Security feature that correlates risk events into investigations. WhisperGraph populators feed into RBA. See [ES Integration](https://www.whisper.security/docs/integrations/splunk/es-integration.md). **CIM (Common Information Model)** — Splunk's standard schema for normalized fields (`src`, `dest`, `user`, etc.). See [CIM Mapping](https://www.whisper.security/docs/integrations/splunk/cim-mapping.md). **AppInspect** — Splunk's automated review for Cloud-eligible apps. The WhisperGraph TA passes AppInspect requirements documented in [Requirements](https://www.whisper.security/docs/integrations/splunk/requirements.md). **Modular input** — A Splunk input that runs on a schedule. WhisperGraph uses modular inputs for owned-domain monitoring and KV-store population. See [Modular Inputs](https://www.whisper.security/docs/integrations/splunk/modular-inputs.md). --- ## API and protocol **Cypher** — A query language for graph databases, originally from Neo4j. WhisperGraph supports a subset focused on read queries. See [Cypher Syntax](https://www.whisper.security/docs/cypher-syntax.md). **MCP (Model Context Protocol)** — Anthropic's open protocol for connecting AI assistants to tools and data sources. WhisperGraph runs an MCP server. See [MCP Client Setup](https://www.whisper.security/docs/mcp/setup.md). **RFC 7807** — IETF standard for HTTP problem-detail responses. WhisperGraph error responses follow this format. See [Error Codes](https://www.whisper.security/docs/reference/error-codes.md). **Anonymous tier** — The unauthenticated tier of the WhisperGraph API. Requests without a valid `X-API-Key` header run here. For limits and pricing see the [pricing page](https://www.whisper.security/pricing). --- If a term is missing or ambiguous, [open a docs issue](https://github.com/whisper-security/docs/issues) or email [docs@whisper.security](mailto:docs@whisper.security). --- ### Brand Protection & Anti-Phishing Markdown: https://www.whisper.security/docs/recipes/brand-protection.md HTML: https://www.whisper.security/docs/recipes/brand-protection You're watching for abuse of your organization's brand in domain registrations, lookalike infrastructure, and phishing kits. #### Quick Triage ### Find Registered Typosquatting Variants Your brand is `paypal.com` and you want to know which look-alike domains have actually been registered — without hand-writing dozens of `STARTS WITH` and `CONTAINS` patterns. ```cypher // Generate typosquatting variants and keep only the registered ones CALL whisper.variants("paypal.com") YIELD variant, method, confidence, confidenceLabel RETURN variant, method, confidence, confidenceLabel LIMIT 20 ``` **Sample output**: ```json [ {"variant": "aypal.com", "method": "OMISSION", "confidence": 0.7, "confidenceLabel": "medium"}, {"variant": "pypal.com", "method": "OMISSION", "confidence": 0.7, "confidenceLabel": "medium"}, {"variant": "papal.com", "method": "OMISSION", "confidence": 0.7, "confidenceLabel": "medium"} ] ``` > **Tip**: `whisper.variants()` runs the domain through more than a dozen generation methods — character omission, repetition, transposition, keyboard-adjacent typos, homoglyphs, bitsquatting, and TLD swaps — and by default returns only variants that are registered. A registered variant is not automatically malicious: pass each hit to `CALL explain()` for a threat verdict, then check where it resolves. ### Brand Name Search Find all domains in the graph that contain your brand name. ```cypher // All domains containing the brand name MATCH (h:HOSTNAME) WHERE h.name CONTAINS "paypal" RETURN h.name LIMIT 20 ``` **Sample output**: ```json [ {"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. ```cypher // Lookalike domains starting with brand- MATCH (h:HOSTNAME) WHERE h.name STARTS WITH "paypal-" RETURN h.name LIMIT 15 ``` **Sample output**: ```json [ {"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 ### Trace Where Typosquatting Variants Point You have a list of registered look-alike domains. The next question is operational: which ones are live, and what infrastructure are they sitting on? This chains the variant generator straight into DNS resolution. ```cypher // Registered variants that resolve, with their IP addresses CALL whisper.variants("paypal.com") YIELD variant, method WITH variant, method MATCH (h:HOSTNAME {name: variant})-[:RESOLVES_TO]->(ip:IPV4) RETURN variant, method, ip.name AS resolves_to LIMIT 20 ``` **Sample output**: ```json [ {"variant": "aypal.com", "method": "OMISSION", "resolves_to": "103.224.212.202"}, {"variant": "pypal.com", "method": "OMISSION", "resolves_to": "199.191.50.130"}, {"variant": "papal.com", "method": "OMISSION", "resolves_to": "100.20.5.222"} ] ``` > **Tip**: A variant that resolves to a parking IP is squatting; one that resolves to a live host with a mail server is a phishing risk. Feed the `resolves_to` addresses into the "Hunt for Co-Hosted Domains" and threat-listing recipes to see whether the variant shares infrastructure with known-bad domains. ### 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. ```cypher // 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. ```cypher // 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. ```cypher // 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. ```cypher // 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. ```cypher // External sites that link to your domain MATCH (source:HOSTNAME)-[:LINKS_TO]->(h:HOSTNAME {name: "cloudflare.com"}) RETURN source.name LIMIT 15 ``` **Sample output**: ```json [ {"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. --- ### Cypher Best Practices Markdown: https://www.whisper.security/docs/cypher-best-practices.md HTML: https://www.whisper.security/docs/cypher-best-practices Performance and correctness guidance for writing Cypher queries against WhisperGraph. Apply these on every query — graph scans on billion-node labels will time out. ## General rules - **Anchor your starting node.** `MATCH (n:HOSTNAME {name: "example.com"})` does an indexed lookup. `MATCH (n:HOSTNAME)` scans billions of nodes. - **Always use LIMIT.** Especially on traversals that could fan out (LINKS_TO, RESOLVES_TO on CDN IPs). Start small and increase if needed. - **Use OPTIONAL MATCH for WHOIS fields.** Not every domain has a registrar, email, or phone. A required MATCH on a missing field returns zero rows and hides other results. - **Use count() before pulling large result sets.** Check cardinality first to avoid unexpectedly large responses. - **Use UNWIND for batch lookups.** Pass lists of indicators in a single request rather than making one request per indicator. - **Specify edge types explicitly.** `[:RESOLVES_TO]` is faster than `[r]` because the engine does not need to check all edge types. - **Use ANNOUNCED_BY for current BGP routing.** Use BELONGS_TO for the registered RIR allocation. They return different prefix types and may give different results. - **Anchor LINKS_TO queries.** The web link graph is one of the largest datasets. Queries without an anchored starting node will time out. - **Avoid CONTAINS with special characters.** Characters like `&` cause slow full-text scans. Use STARTS WITH or exact match when possible. - **Anchor before traversing virtual edges.** `ANNOUNCED_BY`, `ROUTES` into `ANNOUNCED_PREFIX`, `HAS_NAME`, `BELONGS_TO` into `REGISTERED_PREFIX`, `LISTED_IN`, and `CONFLICTS_WITH` are synthesized at query time. They only resolve when the source node of the traversal is anchored — an unanchored probe like `MATCH (a:ASN)-[:HAS_NAME]->(n)` returns nothing. - **Don't put virtual edges in a variable-length pattern.** A pattern like `[:ANNOUNCED_BY*1..3]` will not work — variable-length expansion only walks stored edges. Use fixed-length hops for `ANNOUNCED_BY`, `LISTED_IN`, and `BELONGS_TO` into `REGISTERED_PREFIX`. - **Use `GET /api/query/stats` for global counts.** A query that counts every edge in the graph — `MATCH ()-[r]->() RETURN count(r)` — will time out. The stats endpoint answers global node and edge counts instantly. ## Do this / not that | Do this | Not that | Why | |---------|----------|-----| | `MATCH (h:HOSTNAME {name: "example.com"})` | `MATCH (h:HOSTNAME) WHERE h.name = "example.com"` | Inline property gets an indexed lookup | | Always add `LIMIT` | Open-ended traversals | Prevents timeout on billion-scale labels | | `OPTIONAL MATCH` for WHOIS fields | `MATCH` for sparse relationships | Avoids losing rows when fields are missing | | `MATCH (sub)-[:CHILD_OF]->(h {name: "x.com"})` | `WHERE h.name ENDS WITH ".x.com"` | CHILD_OF uses an indexed edge; ENDS WITH scans | | `STARTS WITH "www.goo"` | `=~ "www\\.goo.*"` | STARTS WITH uses the FST index; regex does not | | Pre-compute in WITH, then ORDER BY alias | COUNT{} or COLLECT{} in ORDER BY | Subquery expressions in ORDER BY are not supported | | `GET /api/query/stats` for global counts | `MATCH ()-[r]->() RETURN count(r)` | A whole-graph edge count times out | | Anchor one end, then traverse a virtual edge | Unanchored or variable-length virtual-edge traversal | Virtual edges are synthesized at query time from the anchored node | --- ### DNS & Email Security Markdown: https://www.whisper.security/docs/recipes/dns-email.md HTML: https://www.whisper.security/docs/recipes/dns-email You're auditing DNS configurations, validating SPF coverage, and checking DNSSEC deployment. #### Quick Triage ### Nameserver Inventory Get all nameservers currently delegated for a domain. ```cypher // Authoritative nameservers for a domain MATCH (ns:HOSTNAME)-[:NAMESERVER_FOR]->(h:HOSTNAME {name: "microsoft.com"}) RETURN ns.name LIMIT 10 ``` **Sample output**: ```json [ {"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. ```cypher // MX records for a domain MATCH (mx:HOSTNAME)-[:MAIL_FOR]->(h:HOSTNAME {name: "google.com"}) RETURN mx.name ``` **Sample output**: ```json [{"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. ```cypher // 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**: ```json [ {"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. ```cypher // 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**: ```json [{ "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. ```cypher // 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**: ```json [ {"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. ```cypher // 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**: ```json [ {"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. ```cypher // 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**: ```json [ {"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. ```cypher // 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**: ```json [{"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. ```cypher // 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**: ```json [{"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. --- ## Splunk equivalents For SPF/DMARC posture audits and dangling DNS detection in SPL, see [Splunk Use Cases](https://www.whisper.security/docs/integrations/splunk/use-cases.md). The `whisper_spf_chain` and `whisper_cname_chain` macros wrap the same Cypher patterns — see [Investigation Macros](https://www.whisper.security/docs/integrations/splunk/macros.md). --- ### Cypher Cheat Sheet Markdown: https://www.whisper.security/docs/cheat-sheet.md HTML: https://www.whisper.security/docs/cheat-sheet ### Endpoint ``` POST https://graph.whisper.security/api/query Content-Type: application/json X-API-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 | | `TLD_OPERATOR` | TLD registry operator | | `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 (39 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` | `ASN/CITY/IPV4/PHONE/ANNOUNCED_PREFIX/REGISTERED_PREFIX → COUNTRY` | Country code (HOSTNAME→COUNTRY exists but is sparse) | | `LOCATED_IN` | `IPV4 → CITY` | GeoIP (chain via `HAS_COUNTRY` to reach COUNTRY) | | `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 | | `CONFLICTS_WITH` | `PREFIX → ASN` | MOAS conflict (virtual) | | `OPERATES` | `TLD_OPERATOR → TLD` | Registry operator (virtual) | ### Common Edge-Direction Mistakes These edges cause the most confusion in practice. Edge directions are strict — a wrong-direction traversal returns zero rows with no error. | Edge | Direction | |---|---| | `RESOLVES_TO` | `HOSTNAME → IPV4` (no reverse-PTR edge) | | `LOCATED_IN` | `IPV4 → CITY` (chain via `HAS_COUNTRY` to reach COUNTRY) | | `HAS_COUNTRY` | `ASN/CITY/IPV4/PHONE/ANNOUNCED_PREFIX/REGISTERED_PREFIX → COUNTRY` | | `CHILD_OF` | child → parent (variable-length walks include TLDs) | | `MAIL_FOR` / `NAMESERVER_FOR` | server → domain (use `<-[:MAIL_FOR]-` for a domain's mail servers) | | `BELONGS_TO` | `FEED_SOURCE → CATEGORY` for feeds; `IPV4 → PREFIX` for IPs (never `IN_CATEGORY`) | ### Procedures | Procedure | What It Does | |-----------|-------------| | `CALL explain("indicator")` | Threat score and explanation (IP, domain, ASN, CIDR, hash) | | `CALL whisper.variants("domain")` | Registered typosquatting variants of a domain | | `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 | | `GET /api/query/stats` for global 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 | --- ### Network & BGP Security Markdown: https://www.whisper.security/docs/recipes/bgp-routing.md HTML: https://www.whisper.security/docs/recipes/bgp-routing 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. ```cypher // 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**: ```json [{"asn": "AS13335", "network_name": "CLOUDFLARENET - Cloudflare, Inc."}] ``` ```cypher // 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**: ```json [{"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. ```cypher // Direct BGP peers of an ASN MATCH (a:ASN {name: "AS13335"})-[:PEERS_WITH]->(peer:ASN) RETURN peer.name LIMIT 20 ``` **Sample output**: ```json [ {"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. ```cypher // 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**: ```json [{"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. ```cypher // 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**: ```json [ {"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. ```cypher // 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**: ```json [{"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. ```cypher // 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**: ```json [{"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. ```cypher // Full threat reputation for an ASN CALL explain("AS60729") ``` **Sample output**: ```json [{ "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. ```cypher // Historical BGP routing records for a prefix CALL whisper.history("1.1.1.0/24") ``` **Sample output** (first 3 of many routing snapshots): ```json [ {"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 ```cypher // All five RIRs MATCH (rir:RIR) RETURN rir.name ORDER BY rir.name ``` **Sample output**: ```json [ {"rir.name": "AFRINIC"}, {"rir.name": "APNIC"}, {"rir.name": "ARIN"}, {"rir.name": "LACNIC"}, {"rir.name": "RIPENCC"} ] ``` --- ## Splunk equivalents For BGP hijack detection in Splunk dashboards, see [Splunk Use Cases](https://www.whisper.security/docs/integrations/splunk/use-cases.md) and [Splunk Dashboards Reference](https://www.whisper.security/docs/integrations/splunk/dashboards.md). --- ### Compliance & Risk Assessment Markdown: https://www.whisper.security/docs/recipes/compliance.md HTML: https://www.whisper.security/docs/recipes/compliance 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. ```cypher // 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**: ```json [{"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. ```cypher // Current registrar for a domain MATCH (h:HOSTNAME {name: "stripe.com"})-[:HAS_REGISTRAR]->(r:REGISTRAR) RETURN h.name, r.name ``` **Sample output**: ```json [{"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. ```cypher // 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**: ```json [ {"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. ```cypher // 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**: ```json [{ "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. ```cypher // 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. ```cypher // Historical WHOIS record for evidence collection CALL whisper.history("google.com") ``` **Sample output** (first 2 of 36 snapshots): ```json [ {"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. --- ### Cyber Insurance & Third-Party Risk Markdown: https://www.whisper.security/docs/recipes/insurance-risk.md HTML: https://www.whisper.security/docs/recipes/insurance-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. ```cypher // 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**: ```json [{ "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. ```cypher // 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. ```cypher // 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**: ```json [{"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. ```cypher // Composite threat assessment for a domain CALL explain("cloudflare.com") ``` **Sample output**: ```json [{ "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. --- ## Splunk equivalents For third-party risk scoring inside Splunk, see [Splunk Use Cases](https://www.whisper.security/docs/integrations/splunk/use-cases.md) and the `whisper_explain` macro in [Investigation Macros](https://www.whisper.security/docs/integrations/splunk/macros.md). --- ### Setup Markdown: https://www.whisper.security/docs/mcp/setup.md HTML: https://www.whisper.security/docs/mcp/setup Whisper MCP gives AI assistants access to WhisperGraph, a graph database of internet infrastructure: tens of billions of nodes and edges. DNS, BGP, IP allocation, WHOIS, threat intel. The AI writes Cypher, runs it, and gives you results. This page walks you through connecting an MCP client to `https://mcp.whisper.security`. For the full surface (5 tools, 6 resources, 7 prompts) and example questions, see the [MCP Reference](https://www.whisper.security/docs/mcp/reference.md). ## Setup ### Claude Desktop Add it through **Settings → Connectors**: 1. Open **Settings → Connectors** 2. Click **Add Connector** 3. Enter the URL: `https://mcp.whisper.security` 4. Sign in with your [Whisper Security](https://console.whisper.security) account ### Claude Code ```bash claude mcp add --transport http whisper-graph https://mcp.whisper.security ``` Opens your browser for sign-in on first use. Scope options: - `--scope user` -- all projects - `--scope project` -- current project only - `--scope local` -- this machine, this project (default) ### Cursor 1. Open **Settings → MCP** 2. Click **Add new global MCP server** 3. Paste: ```json { "mcpServers": { "whisper-graph": { "url": "https://mcp.whisper.security" } } } ``` 4. Save and restart Cursor For team-wide config, use `.cursor/mcp.json` in the project root instead. ## VS Code (GitHub Copilot) Create `.vscode/mcp.json` in your project root: ```json { "servers": { "whisper-graph": { "type": "http", "url": "https://mcp.whisper.security" } } } ``` For user-level config (all projects), use Command Palette > **MCP: Add Server**. ### Windsurf 1. Open **Settings** (Cmd+, on Mac, Ctrl+, on Windows) 2. Search for **MCP** 3. Click **View raw config** 4. Add: ```json { "mcpServers": { "whisper-graph": { "serverUrl": "https://mcp.whisper.security" } } } ``` 5. Save and restart Windsurf uses `serverUrl` instead of `url`. ### Antigravity 1. Click **...** at the top of the chat panel 2. Click **MCP Servers** > **Manage MCP Servers** > **View raw config** 3. Add to `mcp_config.json`: ```json { "mcpServers": { "whisper-graph": { "serverUrl": "https://mcp.whisper.security" } } } ``` 4. Go back to Manage MCP Servers and click refresh Config file: `~/.gemini/antigravity/mcp_config.json` ### ChatGPT 1. Go to [chatgpt.com → Settings → Connectors](https://chatgpt.com/#settings/Connectors) 2. Click **Add Connector** 3. Enter the URL: `https://mcp.whisper.security` 4. Complete the sign-in ChatGPT supports OAuth only. ### OpenAI Codex Add to `~/.codex/config.toml`: ```toml [mcp_servers.whisper-graph] url = "https://mcp.whisper.security" ``` Then authenticate: ```bash codex mcp login whisper-graph ``` ### Other clients Any MCP client that speaks Streamable HTTP works. | Transport | URL | |-----------|-----| | Streamable HTTP | `https://mcp.whisper.security` | | SSE | `https://mcp.whisper.security/sse` | For STDIO-only clients, use the `mcp-remote` bridge: ```bash npx mcp-remote https://mcp.whisper.security ``` --- ### Connecting through API key If your client doesn't support OAuth, you can authenticate with an API key instead. 1. Go to [console.whisper.security](https://console.whisper.security) and generate an API key 2. Pass it as a `Bearer` token in the `Authorization` header Most MCP clients let you set custom headers in their config. Add: ``` Authorization: Bearer YOUR_API_KEY ``` For STDIO-only clients, use the `mcp-remote` bridge with the `--header` flag: ```bash npx mcp-remote https://mcp.whisper.security --header "Authorization: Bearer YOUR_API_KEY" ``` Keep your API key out of version control. ## Validating your connection Once the connector is registered, ask the assistant: > "List the WhisperGraph node labels." The MCP client should invoke the `list_labels` tool and return roughly 20 labels (HOSTNAME, IPV4, IPV6, ASN, ANNOUNCED_PREFIX, FEED_SOURCE, …) with live counts. If you instead get an apology or a hand-written list of what the assistant *thinks* the schema looks like, the tool isn't connected — recheck the connector URL and re-authenticate. ## Next steps - [MCP Reference](https://www.whisper.security/docs/mcp/reference.md) — every tool, resource, and prompt the connector exposes, plus example questions and the schema-introspection pattern AI agents should use before writing queries. - [Cypher API Reference](https://www.whisper.security/docs/cypher-api-reference.md) — if you'd rather call the graph directly over REST without MCP. --- [![Whisper Graph on Smithery](https://smithery.ai/badge/whisper-security/whisper-graph)](https://smithery.ai/servers/whisper-security/whisper-graph) --- ### Law Enforcement & Cybercrime Markdown: https://www.whisper.security/docs/recipes/law-enforcement.md HTML: https://www.whisper.security/docs/recipes/law-enforcement 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. ```cypher // 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**: ```json [{"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. ```cypher // 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**: ```json [{ "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 ### Related Domain Discovery via Shared Contact Pivot from a known suspicious domain to related infrastructure through shared registration attributes. ```cypher // 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. ```cypher // Pages this domain links to MATCH (h:HOSTNAME {name: "github.com"})-[:LINKS_TO]->(target:HOSTNAME) RETURN target.name LIMIT 20 ``` **Sample output**: ```json [ {"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. ```cypher // 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. --- ### Security Researchers & Academics Markdown: https://www.whisper.security/docs/recipes/research.md HTML: https://www.whisper.security/docs/recipes/research 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. ```cypher // All node labels with counts CALL db.labels() ``` **Sample output** (first 5 of 20): ```json [ {"label": "HOSTNAME", "count": 2631997144}, {"label": "IPV4", "count": 618914961}, {"label": "EMAIL", "count": 237065663}, {"label": "ORGANIZATION", "count": 119189847}, {"label": "PHONE", "count": 60194142} ] ``` ```cypher // All edge types with counts CALL db.relationshipTypes() ``` **Sample output** (first 5 of 24): ```json [ {"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 39 threat intelligence feeds indexed in the graph. ```cypher // Complete threat feed catalog MATCH (f:FEED_SOURCE) RETURN f.name ORDER BY f.name ``` **Sample output** (first 10 of 39): ```json [ {"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": "CERT.pl Domains"} ] ``` The full feed list also includes: 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. ```cypher // All threat categories MATCH (c:CATEGORY) RETURN c.name ORDER BY c.name ``` **Sample output**: ```json [ {"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. ```cypher // All DNSSEC signing algorithm types in the schema MATCH (algo:DNSSEC_ALGORITHM) RETURN collect(algo.name) AS algorithms ``` **Sample output**: ```json [{"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. ```cypher // 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**: ```json [ {"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. ```cypher // 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. ```cypher // 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**: ```json [{"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 Markdown: https://www.whisper.security/docs/recipes/cross-cutting.md HTML: https://www.whisper.security/docs/recipes/cross-cutting Patterns that work across multiple use cases and personas. ### CNAME Chain Traversal Follow an arbitrary CNAME chain to find the canonical hostname. ```cypher // 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**: ```json [{"alias": "www.github.com", "canonical_host": "github.com"}] ``` ### Registered Allocation for an IP ```cypher // 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**: ```json [{"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. ```cypher // Threat assessment for a CIDR range CALL explain("185.220.101.0/24") ``` **Sample output**: ```json [{ "indicator": "185.220.101.0/24", "type": "network", "found": true, "score": 0.0, "level": "MEDIUM", "explanation": "Network 185.220.101.0/24 contains 176 listed IP(s) and 28 listed subnet(s). Threat density: 68.7500%. Score 74.7 (Medium risk - investigate further).", "factors": [ "Listed IPs: 176 IPs found → 10 × log₂(176 + 1) = 74.68", "Listed subnets: 28 found, max score 4.44 → contributes 3.55 (80% inheritance)", "Threat density: 176 listed / 256 addresses = 68.7500%" ] }] ``` > **Tip**: Scoring a whole network is heavier than scoring a single IP. If the response comes back with `"available": false` and a `retryAfter` value, the assessment timed out — wait that many seconds and run it again. The result is cached once computed, so the retry is fast. ### Find Domains Linking Out to a Suspicious Site ```cypher // Who links to a suspicious domain? MATCH (source:HOSTNAME)-[:LINKS_TO]->(h:HOSTNAME {name: "cloudflare.com"}) RETURN source.name LIMIT 15 ``` ### Classify an Indicator by Threat Category You want more than a yes/no — you want to know *what kind* of threat an IP represents: command-and-control, malware hosting, a scanner, an anonymizer, and so on. Threat-enriched IP and hostname nodes carry category flags you can read directly. ```cypher // Read threat category flags off an IP node MATCH (ip:IPV4 {name: "185.220.101.1"}) RETURN ip.name, ip.threatScore, ip.threatLevel, ip.isThreat, ip.isTor, ip.isProxy, ip.isAnonymizer, ip.isMalware, ip.isC2 ``` **Sample output**: ```json [{ "ip.name": "185.220.101.1", "ip.threatScore": 4.4, "ip.threatLevel": "MEDIUM", "ip.isThreat": true, "ip.isTor": true, "ip.isProxy": false, "ip.isAnonymizer": true, "ip.isMalware": false, "ip.isC2": false }] ``` > **Tip**: Category flags include `isThreat`, `isC2`, `isMalware`, `isPhishing`, `isSpam`, `isTor`, `isProxy`, `isAnonymizer`, `isScanner`, `isBruteforce`, `isBlacklist`, and `isWhitelist`. They are fastest when you anchor on a specific node with `{name: "..."}` — filtering an unanchored label scan on a flag will time out. To find indicators of a given category, start from a known node or prefix and traverse outward. --- ### Programmatic Signup (for Agents) Markdown: https://www.whisper.security/docs/agent-signup.md HTML: https://www.whisper.security/docs/agent-signup Whisper is designed for AI agents to discover, sign up for, and start using without a human in the loop. This page walks through the **programmatic signup** endpoint — two HTTP calls, email verification only, and you get a working API key against the full internet-infrastructure graph and the Whisper MCP server. If you're a human looking for the regular signup form, head to [console.whisper.security/sign-up](https://console.whisper.security/sign-up) instead. ## What you get Every signup — programmatic or browser-based — provisions: - An API key (`whisper-…`) valid against `graph.whisper.security` and `mcp.whisper.security`. - A free **Trial** plan with no time limit. You can later upgrade to Starter, Professional, Business, or Enterprise from the [console billing page](https://console.whisper.security/billing). - A console dashboard at `console.whisper.security` where the human owner of the email can see usage, manage keys, and upgrade. Whisper is intentionally permissive at signup. No CAPTCHA, no domain blocklist, no per-IP rate limiting. We'll add abuse mitigations only if we see actual abuse hurting us. ## The two-call flow ### 1. Start the signup ```bash curl -s -X POST https://console.whisper.security/api/signup \ -H "Content-Type: application/json" \ -d '{ "email": "your-agent@example.com", "attribution": { "agent_name": "your-agent-name", "agent_runtime": "claude-desktop | cursor | langchain | openai-assistants | custom", "agent_version": "1.2.3", "source": "smithery | mcp-directory | self | blog-post" } }' ``` Response: ```json { "signup_id": "...", "expires_at": "2026-05-16T18:00:00Z" } ``` Whisper emails a 6-digit verification code to the address you provided. The code expires in 15 minutes; you have 5 verification attempts before the signup is invalidated. The `attribution` block is **optional and never gating**. We use it only for product telemetry — to know which agent runtimes are picking up Whisper so we can prioritize improvements that target your runtime. Set whatever values make sense; nothing is rejected. ### 2. Verify the code ```bash curl -s -X POST https://console.whisper.security/api/signup/verify \ -H "Content-Type: application/json" \ -d '{ "signup_id": "", "code": "" }' ``` Response: ```json { "user_id": "user_...", "api_key": "whisper-...", "plan": "trial", "mcp_url": "https://mcp.whisper.security", "docs_url": "https://www.whisper.security/docs/agent-signup", "dashboard_url": "https://console.whisper.security" } ``` The returned `api_key` is immediately usable. No further setup required. ### 3. Use the key Against the graph DB: ```bash curl -s https://graph.whisper.security/api/query \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"query": "MATCH (h:HOSTNAME {name: \"example.com\"})-[:RESOLVES_TO]->(ip) RETURN ip.name LIMIT 10"}' ``` Against the MCP server — drop this into your Claude Desktop / Cursor / VS Code MCP client config: ```json { "mcpServers": { "whisper": { "url": "https://mcp.whisper.security", "headers": { "Authorization": "Bearer " } } } } ``` ## Node / TypeScript ```typescript const signup = await fetch('https://console.whisper.security/api/signup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'your-agent@example.com', attribution: { agent_name: 'my-agent', source: 'self' }, }), }).then((r) => r.json()); // ... fetch the code from your inbox ... const { api_key } = await fetch('https://console.whisper.security/api/signup/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ signup_id: signup.signup_id, code: process.env.CODE }), }).then((r) => r.json()); const result = await fetch('https://graph.whisper.security/api/query', { method: 'POST', headers: { Authorization: `Bearer ${api_key}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ query: 'MATCH (h:HOSTNAME {name: "example.com"})-[:RESOLVES_TO]->(ip) RETURN ip.name LIMIT 10', }), }).then((r) => r.json()); ``` ## Python ```python import os, requests signup = requests.post( "https://console.whisper.security/api/signup", json={ "email": "your-agent@example.com", "attribution": {"agent_name": "my-agent", "source": "self"}, }, ).json() ## ... fetch the code from your inbox ... verified = requests.post( "https://console.whisper.security/api/signup/verify", json={"signup_id": signup["signup_id"], "code": os.environ["CODE"]}, ).json() api_key = verified["api_key"] result = requests.post( "https://graph.whisper.security/api/query", headers={"Authorization": f"Bearer {api_key}"}, json={ "query": 'MATCH (h:HOSTNAME {name: "example.com"})-[:RESOLVES_TO]->(ip) RETURN ip.name LIMIT 10' }, ).json() ``` ## Error responses - **`400 captcha_missing_token`** — Bot sign-up protection is currently enabled on the Whisper Clerk instance. This shouldn't happen in normal operation. If you hit it, contact `support@whisper.security`. - **`400 verification_failed`** — Wrong code. Response includes `attempts_remaining`. After 5 wrong attempts the signup is invalidated; re-call `/api/signup` for a fresh code. - **`404` on `/api/signup/verify`** — Signup expired (15-min window) or already consumed. Re-call `/api/signup`. - **`429` on `/api/signup/verify`** — Too many attempts; signup invalidated. Re-call `/api/signup`. ## Limits and quotas The Trial plan applies to all new signups. Per the [Whisper MCP plan limits](https://mcp.whisper.security): - Free / Trial: 10 req/min, 500 req/day, max LIMIT 25 per Cypher query. - Paid plans lift these caps — see [pricing](https://www.whisper.security/pricing) and [console billing](https://console.whisper.security/billing). For ad-hoc queries that don't need an API key, the anonymous graph endpoint at `https://graph.whisper.security/api/query` allows 2-hop traversal, 100 queries/hour, 5-second timeout — no signup required. ## See also - [Cypher query guide](https://www.whisper.security/docs/cypher-query-guide.md) — the full Cypher reference for the Whisper graph. - [MCP reference](https://www.whisper.security/docs/mcp/reference.md) — every tool the Whisper MCP server exposes. - [Pricing](https://www.whisper.security/pricing) — Trial through Enterprise. - [`/llms-full.txt`](https://www.whisper.security/llms-full.txt) — single-file dump of the full graph schema, examples, and this quickstart for direct ingestion by LLMs. --- ### Splunk Integration (moved) Markdown: https://www.whisper.security/docs/integrations/splunk.md HTML: https://www.whisper.security/docs/integrations/splunk > This page has moved. See the canonical [Splunk Integration Overview](https://www.whisper.security/docs/integrations/splunk/overview.md). --- ### Reference Markdown: https://www.whisper.security/docs/mcp/reference.md HTML: https://www.whisper.security/docs/mcp/reference Reference for everything the [Whisper MCP server](https://www.whisper.security/docs/mcp/setup.md) exposes: 6 tools, 6 resources, 8 prompts, plus example questions agents can answer with them. For client-by-client install instructions, see the [Setup guide](https://www.whisper.security/docs/mcp/setup.md). The graph behind the connector holds **7.39 billion nodes, 39 billion edges, and 5.6 million threat-intel edges** across 20 entity types. ## What's exposed The connector advertises **6 tools**, **6 resources**, and **8 prompts**. All tools are read-only. ### Tools (6) | Tool | Purpose | |------|---------| | `query` | Primary Cypher tool — runs an arbitrary Cypher query against the graph and returns rows | | `list_labels` | Schema introspection — returns every node label with its count and indexed properties | | `describe_label(label)` | Property metadata for a single label — names, types, indexed flag, in/out edge types | | `explain_indicator(indicator)` | Threat assessment for an IP / hostname / ASN / CIDR (composite score, level, factors) | | `whisper_history(indicator)` | Historical WHOIS or BGP snapshots for an indicator | | `domain_variants(name, [label], [includeNonExistent])` | Typosquatting / brand-protection variant generation — see below | `list_labels` and `describe_label` are cached server-side for five minutes, so the agent can call them as often as it needs to without paying a quota cost — see [Schema introspection](#schema-introspection) below for the recommended pattern. #### `domain_variants` — typosquatting & brand protection `domain_variants(name, [label], [includeNonExistent])` runs 14 mutation algorithms (character omission, repetition, transposition, QWERTY-adjacent replacement/insertion, vowel-swap, bitsquatting, homoglyph / Unicode confusables, hyphenation, dot insertion/omission, TLD-swap, TLD-addition, subdomain-add) and **by default returns only the lookalike domains that are actually registered in the graph**. Each row: `variant, method, exists, nodeId, label, confidence (0.3–0.9), confidenceLabel`. Arguments: - `name` (required) — the domain to generate variants for. Unicode / IDN input is accepted. - `label` (optional, default `HOSTNAME`) — the node label to check variants against. - `includeNonExistent` (optional boolean, default `false`) — set `true` to also return generated variants that are *not* registered in the graph. Note: `exists` means *registered / observed*, **not** *malicious* — pivot hits through `explain_indicator` for a threat verdict. ### Resources (6) | Resource | Contents | |----------|----------| | `whisper://schema/full` | Full schema reference: every label, every edge type, every threat-intel property | | `whisper://schema/relationships` | Entity-relationship map — which labels connect to which, in which direction | | `whisper://guide/functions` | Cypher function reference (aggregations, string, numeric, date/time, schema introspection) | | `whisper://guide/cookbook` | Persona-organised cookbook — 70+ live-validated query patterns across 8 analyst roles | | `whisper://stats` | Live database statistics — nested node and edge counts (`physical` / `virtual` / `total`) plus the `threatIntel` block | | `whisper://quota` | Caller's plan tier, max query depth, current usage | ### Prompts (8) | Prompt | What it does | |--------|--------------| | `investigate-ip` | Walks an IP through prefix → ASN → owner → threat intel → reverse-DNS | | `map-attack-surface` | Maps a domain's external attack surface: subdomains, hosting, mail, SPF, nameservers | | `compare-domains` | Compares the infrastructure of two or more domains side-by-side | | `blast-radius` | ASN failure-impact analysis: prefixes, peers, downstream domains affected if the ASN goes down | | `threat-triage` | Comprehensive indicator triage — pulls feed listings, score, level, and historical context in one pass | | `whois-pivot` | Domain pivot via shared WHOIS contacts (registrar, email, phone, organisation) | | `bgp-investigation` | Full ASN profile — peers, prefixes, registered-org, country, MOAS conflicts, threat density | | `typosquat-sweep` | Brand-protection sweep — finds registered lookalikes of a domain, enriches each with threat-feed listings, runs `explain_indicator` on the suspicious ones, and pivots through WHOIS to identify who registered them | ## Agent Skills The eight prompts above are single-shot. For richer, auto-loading investigation workflows — `whisper-investigate`, `whisper-cypher`, and `whisper-brand-protection` — see [Agent Skills](https://www.whisper.security/docs/mcp/skills.md): an open-source ([github.com/whisper-sec/whisper-skills](https://github.com/whisper-sec/whisper-skills), MIT-licensed) skill pack that runs on top of these same tools. ## Schema introspection When an agent isn't sure whether the canonical property is `h.name` or `h.fqdn`, or whether to anchor on `HOSTNAME` or `DOMAIN`, it should call `list_labels` and `describe_label` *before* writing the query. Both tools are cached for five minutes server-side and return in milliseconds — they're cheap to call and prevent the most common query bug (writing a defensive `MATCH (h) WHERE h:HOSTNAME OR h:DOMAIN OR h:FQDN ...` against a billion-node label, which the validator rejects). A typical introspection-then-query transcript: ``` User: What labels does the Whisper graph have? Agent: [calls list_labels] → [{name: "HOSTNAME", count: 2_631_997_144, indexed_properties: ["name"]}, {name: "IPV4", count: 618_914_961, indexed_properties: ["name"]}, ... 18 more] Agent: The graph has 20 labels. The biggest are HOSTNAME (2.6B), IPV4 (619M), and ANNOUNCED_PREFIX (1.4M). What do you want to look at? User: Show me the threat properties on a hostname. Agent: [calls describe_label("HOSTNAME")] → {name: "HOSTNAME", count: 2_631_997_144, properties: [{name: "name", type: "String", indexed: true}, {name: "threatScore", type: "Double"}, {name: "threatLevel", type: "String"}, {name: "isThreat", type: "Boolean"}, {name: "isC2", type: "Boolean"}, ...], out_edges: [{type: "RESOLVES_TO", to: "IPV4"}, {type: "CHILD_OF", to: "HOSTNAME"}, {type: "HAS_REGISTRAR", to: "REGISTRAR"}, ...], in_edges: [{type: "NAMESERVER_FOR", from: "HOSTNAME"}, {type: "MAIL_FOR", from: "HOSTNAME"}, ...]} Agent: [calls query] MATCH (h:HOSTNAME {name: "evil.example"}) RETURN h.threatScore, h.threatLevel, h.isThreat, h.isC2, h.isPhishing, h.isMalware ``` Agents that introspect first write valid queries on the first try; agents that don't tend to fall back to defensive label-or'ed scans that the validator rejects. ## Error model The `query` tool returns a typed error envelope that clients and LLMs can branch on instead of parsing free-text messages. | Field | Meaning | |-------|---------| | `success` | `true` on success, `false` on error | | `error` | Human-readable error message | | `suggestion` | A concrete fix the agent can apply and retry | | `errorCode` | Machine-readable code (see below) | | `retryable` | Whether re-running the same query could succeed | `errorCode` is one of: | Code | Cause | Retryable | |------|-------|-----------| | `DB_UNAVAILABLE` | The graph database is unreachable | Yes | | `VALIDATION_REJECTED` | The query failed a query-safety rule (see below) | No — fix the query | | `CYPHER_SYNTAX_ERROR` | The Cypher itself is malformed | No — fix the query | | `DB_TIMEOUT` | The query ran past its time budget | Yes — narrow it | | `QUERY_TOO_EXPENSIVE` | The query was stopped by the database's element / row-budget guard (stopped for *size*, not duration) | No — narrow it | `QUERY_TOO_EXPENSIVE` is the size guard, distinct from `DB_TIMEOUT`'s duration guard — a query can be cheap-per-row but still touch too many elements. ## Procedures callable inside `query` Beyond the wrapper tools, the `query` tool accepts these procedures directly inside Cypher: | Procedure | Equivalent of | Purpose | |-----------|---------------|---------| | `CALL explain("indicator")` | `explain_indicator` | Threat assessment for an IP / hostname / ASN / CIDR | | `CALL whisper.history("indicator")` | `whisper_history` | Historical WHOIS / BGP snapshots | | `CALL whisper.variants("name" [, "LABEL"] [, checkExisting])` | `domain_variants` | Typosquat / lookalike variant generation | | `CALL whisper.quota()` | — | Caller's plan tier and current usage | | `CALL db.labels()` | `list_labels` | Every node label | | `CALL db.relationshipTypes()` | — | Every edge type | | `CALL db.schema("json")` | — | Full schema as JSON | `whisper.variants()` also works in **expression position**, not only as a top-level `CALL` — e.g. `RETURN size(whisper.variants("paypal.com"))` or `MATCH (h:HOSTNAME {name: whisper.variants("paypal.com")[0].variant}) RETURN h`. `explain()` and `whisper.history()` remain `CALL`-only. ## Query-safety rules The server validates every query before running it. A query that breaks one of these rules comes back as `VALIDATION_REJECTED` with a `suggestion`: 1. `shortestPath` must be bounded (no unbounded variable-length path inside it). 2. `LIMIT` must be ≤ 500. 3. No unlabeled `MATCH` — every `MATCH` pattern must carry a node label. 4. No same-variable label disjunction — `WHERE n:A OR n:B` on one variable is rejected (introspect with `describe_label` instead). 5. Indexed text operators (`CONTAINS` / `STARTS WITH` / `ENDS WITH`) are allowed only on the `.name` property. 6. No unanchored scans on large labels — anchor the `MATCH` on an indexed value. 7. Exploration queries must include a `LIMIT`. ## What you can ask The graph has DNS, BGP routing, IP allocation, GeoIP, WHOIS (237M emails, 65M phone numbers), email infrastructure (MX and full SPF chains), DNSSEC, 9.1 billion web hyperlinks, and ~40 threat intel feeds (39 live today; the exact count is available from the `whisper://stats` resource). All of it is connected. The AI walks the edges between them in a single conversation, so you don't have to piece it together yourself. Just ask in plain language. ### Incident response You got an IP or domain from an alert. Start here. - "Investigate 185.220.101.42 -- who owns it, where is it, is it on any threat feeds, and what else is hosted there?" - "This domain showed up in our logs: secure-login-update.com. Is it live? Who registered it? Does the registrant own other domains?" - "We're seeing traffic to 104.16.132.229. Trace it: IP to prefix to ASN to org. Then check if any co-hosted domains are flagged." - "Here are 20 IPs from our SIEM. Which ones are Tor exits, C2, or on blocklists?" ### Threat hunting Any threat feed can tell you an IP is bad. The graph lets you pivot -- follow a bad IP to its ASN, find the other prefixes, check what's hosted there, pull WHOIS on the domains, and see if the registrant has other infrastructure. One conversation. - "Find every domain registered by the same WHOIS contact as secure-login-update.com. Do any share IPs or nameservers?" - "Check AS60729 -- how many of its prefixes have threat-listed IPs? What's the threat density?" - "Are there MOAS conflicts on this prefix? Which ASNs are announcing it?" - "Find all IPs in 185.220.101.0/24 that appear on threat feeds. Group by category." - "What domains resolve to IPs on the Dan Tor Exit feed? Cross-reference with their WHOIS registrants." ### Brand protection and typosquatting The `domain_variants` tool runs 14 mutation algorithms over a domain and returns the lookalikes that are actually registered — homoglyphs, bitsquats, TLD swaps, omissions, and more. Pivot the hits through threat intel and WHOIS to see which ones are live attacks. - "Find registered typosquats of paypal.com." - "Generate lookalike domains for our brand, then check which ones are on threat feeds or resolve to live IPs." - "Which registered variants of microsoft.com share a registrant email or nameserver with each other?" - "Run a typosquat sweep on stripe.com -- who registered the lookalikes and is any of their infrastructure flagged?" ### Attack surface Everything an attacker would look for: subdomains, IPs, mail servers, SPF authorization chains, nameservers, WHOIS. - "Map tesla.com -- subdomains, IPs, ASNs, nameservers, mail servers, SPF includes, and WHOIS registrant." - "What third-party services can send email as netflix.com? Walk the full SPF include chain." - "Find every subdomain of example.com, resolve them, and group by ASN. How many hosting providers?" - "Where does the CNAME chain for www.example.com end up? Who hosts the final target?" ### WHOIS and registrant pivoting This is where investigations get interesting. WHOIS gives you a registrant email or phone number. The graph has 237M emails and 65M phones, so you can follow that contact to every other domain they registered, then check if those domains share hosting. - "Find the WHOIS registrant for secure-login-update.com, then every other domain they registered. Do any share infrastructure?" - "What domains use this contact email? Show their IPs and ASNs, and flag any that are threat-listed." - "Has google.com changed registrars? Show the history." - "Find domains registered with the same phone number. Any overlap in hosting?" - "Compare WHOIS for these five domains -- same registrant? Same email? Same registrar?" ### BGP and routing 115K ASNs, 2.5M prefixes, full peering topology. - "If AS16509 (Amazon) went down, how many prefixes and peers are affected? What domains go dark?" - "Which ASNs peer with both Cloudflare and Google?" - "Show the BGP routing history for 8.8.8.0/24. Has the announcing ASN changed?" - "Find prefixes with MOAS conflicts announced by AS60729. Any of them hosting threat-listed IPs?" - "What RIR allocated this prefix? Which org registered it?" ### Comparing infrastructure The thing that's hard to do anywhere else: checking whether two domains share anything. Same IPs, same ASN, same nameservers, same registrant email, same phone number. The graph checks all of it at once. - "Do pandas-crossing.com and afterlifeevents.com share any infrastructure?" - "These three phishing domains were reported separately. Any shared nameservers, IPs, ASNs, or WHOIS contacts?" - "Compare the hosting and email setup of these two competing SaaS products." - "Find domains that share both the same registrant email and the same IP range as this known-bad domain." ### Email and SPF The graph stores the full SPF record structure -- includes, ip4, a, mx, exists, redirect -- as separate edges. So you can walk the authorization chain rather than parsing TXT records by hand. - "Who can send email as shopify.com? Walk the SPF chain." - "What domains use the same SPF include targets as this phishing domain?" - "Does this domain have MX records? SPF? Give me the full email setup." ### GeoIP and data residency 619M IPv4 addresses mapped to cities and countries. - "Where are all the IPs that example.com resolves to? List by country." - "Does this company host anything in sanctioned countries? Check all their domain IPs." - "Find all IPs in this ASN that geolocate to Russia." ### Web links 9.1 billion hyperlinks from Common Crawl. - "What external domains does google.com link to? Where are those hosted?" - "Who links to this suspicious domain? Are any of the linking sites threat-listed?" - "Do these two domains link to each other?" ### DNSSEC - "Is cloudflare.com signed with DNSSEC? What algorithm?" - "What percentage of domains under this nameserver use DNSSEC?" ### History WHOIS and BGP changes over time. - "Show the WHOIS history for google.com -- registrar changes, nameserver updates, ownership." - "BGP routing history for 8.8.8.8 -- has the announcing ASN or prefix changed?" - "When was this domain registered? Has it changed hands?" ## Try these prompts The connector ships eight slash-style prompts. Four of them are recent additions worth a try: ### `threat-triage` Comprehensive indicator triage in one pass — feed listings, composite score, level, recency, historical sightings. ``` /threat-triage 185.220.101.1 ``` This indicator is a TOR exit node listed in four feeds (Dan Tor Exit, Tor Exit Nodes, FireHOL Level 2, Spamhaus DROP). The prompt returns the full feed list, score breakdown, first/last seen across all feeds, and the upstream ASN's reputation. ### `whois-pivot` Pivots from a single domain through every shared WHOIS contact (registrar, email, phone, organisation) to find related infrastructure. ``` /whois-pivot cloudflare.com ``` Returns: every other domain registered with the same email, phone, or organisation; whether any of those domains share IPs or nameservers; flags on threat-listed neighbours. ### `bgp-investigation` Full ASN profile — peers, prefixes, MOAS conflicts, registered organisation, country, threat density inside the network. ``` /bgp-investigation AS13335 ``` For Cloudflare's ASN, the prompt returns peer count, prefix inventory (sample), MOAS detections, country of registration, and how many of the routed IPs appear in threat feeds. ### `typosquat-sweep` Brand-protection sweep — finds registered lookalikes of a domain with `domain_variants`, enriches each with threat-feed listings, runs `explain_indicator` on the suspicious ones, and pivots through WHOIS to identify who registered them. ``` /typosquat-sweep paypal.com ``` Returns: every registered variant of the domain (with the mutation method and a confidence score), which of those variants are on threat feeds or resolve to live infrastructure, and the WHOIS registrant behind the suspicious ones. --- ### Splunk Integration Markdown: https://www.whisper.security/docs/integrations/splunk/overview.md HTML: https://www.whisper.security/docs/integrations/splunk/overview Whisper Splunk connects your Splunk environment to WhisperGraph — billions of nodes, tens of billions of edges, and millions of threat intelligence edges across 40+ feed sources. Enrich IOCs, run ad-hoc graph queries, populate ES threat intel, and monitor your owned attack surface — all from within Splunk. > **Get the add-on:** [Whisper Security TA on Splunkbase →](https://splunkbase.splunk.com/app/8695) --- ## What you get **IOC enrichment** — Enrich IPs, domains, and hostnames in your Splunk events with threat intelligence, WHOIS, BGP routing, and geolocation. Streaming command (`whisperlookup`) processes events inline. **Ad-hoc graph queries** — Run Cypher queries directly from the Splunk search bar with `whisperquery`. Trace infrastructure relationships, pivot across DNS, IP, ASN, and registration data without leaving Splunk. **Threat intelligence** — Automated feeds populate KV Store collections with scored threat data. Integrates natively with Splunk Enterprise Security's threat-intel framework for risk-based alerting. **Attack surface monitoring** — Scheduled modular inputs continuously monitor your domains, IPs, and ASNs for changes in DNS, routing, WHOIS, and threat-feed status. Alerts on new exposures automatically. **Dashboards and reporting** — Pre-built dashboards for threat overview, enrichment activity, API health, and investigation workflows. Customizable with Splunk's dashboard framework. The add-on focuses on three workflows: enrich your logs, investigate one indicator interactively, and monitor your owned domains. It does not ship a broad prebuilt detection pack and does not require Splunk ES by default. --- ## Components | Component | Description | |-----------|-------------| | **TA-whisper-graph** | Technology Add-on — custom search commands, modular inputs, KV Store caching, enrichment, investigation dashboard, attack-surface and compliance dashboards | | **ES Integration ** | Threat-intel KV Store populators and example enrichment-to-risk pipelines. Opt-in, disabled by default. | ## Search commands | Command | Type | Description | |---------|------|-------------| | `whisperlookup` | Streaming | Enrich events with IOC context from WhisperGraph | | `whisperquery` | Generating | Execute ad-hoc Cypher queries against WhisperGraph | | `whisperschema` | Generating | Explore the graph schema (labels, relationships, properties, metadata) | | `whisperflush` | Generating | Flush the enrichment cache | See the full [search commands reference](https://www.whisper.security/docs/integrations/splunk/search-commands.md). ## Pre-built investigation macros | Macro | Description | |-------|-------------| | `whisper_shared_nameservers(domain)` | Find domains sharing nameservers | | `whisper_asn_infrastructure(asn)` | Enumerate prefixes and hostnames behind an ASN | | `whisper_cname_chain(domain)` | Resolve CNAME chain (up to 5 hops) | | `whisper_spf_chain(domain)` | Trace SPF include chain | | `whisper_bgp_peers(asn)` | List BGP peers | | `whisper_cohosted_domains(domain)` | Find co-hosted domains | | `whisper_full_investigation(indicator)` | Full infrastructure investigation | | `whisper_explain(indicator)` | Get threat assessment | See the [investigation macros reference](https://www.whisper.security/docs/integrations/splunk/macros.md). ## Saved searches The add-on ships only the searches needed for the three workflows above. The broad prebuilt detection pack was removed in favour of disabled example enrichment templates customers can clone and tailor. | Search | Kind | Default | |--------|------|---------| | Whisper - Evict Expired Cache Entries | Utility | Disabled | | Whisper - Populate IP Threat Intel KV Store | ES populator | Disabled | | Whisper - Populate Domain Threat Intel KV Store | ES populator | Disabled | | Whisper - Populate Precomputed Enrichment KV Store | Utility | Disabled | | Example - Whisper - Enrich DNS Domains | Enrichment template | Disabled | | Example - Whisper - Enrich Destination IPs | Enrichment template | Disabled | | Example - Whisper - Enrich Proxy Hostnames | Enrichment template | Disabled | | Example - Whisper - Custom Graph Query Enrichment | Enrichment template | Disabled | See the [saved searches reference](https://www.whisper.security/docs/integrations/splunk/saved-searches.md). --- ## Getting started | Step | Guide | |------|-------| | 1. Check requirements | [Requirements](https://www.whisper.security/docs/integrations/splunk/requirements.md) | | 2. Install the add-on | [Installation](https://www.whisper.security/docs/integrations/splunk/installation.md) | | 3. Configure API key | [Configuration](https://www.whisper.security/docs/integrations/splunk/configuration.md) | | 4. Start enriching events | [Search Commands](https://www.whisper.security/docs/integrations/splunk/search-commands.md) | --- ## Documentation index ### Setup - [Requirements](https://www.whisper.security/docs/integrations/splunk/requirements.md) — Software versions, network access, and permissions - [Installation](https://www.whisper.security/docs/integrations/splunk/installation.md) — Single-instance, distributed, and Splunk Cloud deployment - [Deployment Architecture](https://www.whisper.security/docs/integrations/splunk/deployment-architecture.md) — Enterprise patterns: SHC, deployment server, indexer clusters - [Configuration](https://www.whisper.security/docs/integrations/splunk/configuration.md) — API key, proxy, caching, and modular input settings ### Core features - [Search Commands](https://www.whisper.security/docs/integrations/splunk/search-commands.md) — `whisperlookup` and `whisperquery` reference - [Enrichment Pipeline](https://www.whisper.security/docs/integrations/splunk/enrichment.md) — How IOC enrichment works end to end - [Lookups](https://www.whisper.security/docs/integrations/splunk/lookups.md) — KV Store lookup tables and automatic enrichment - [Modular Inputs](https://www.whisper.security/docs/integrations/splunk/modular-inputs.md) — Scheduled data collection (threat intel, baselines, watchlists) - [Saved Searches](https://www.whisper.security/docs/integrations/splunk/saved-searches.md) — Example enrichment templates, KV Store populators, and how to build your own detections - [Dashboards](https://www.whisper.security/docs/integrations/splunk/dashboards.md) — Pre-built views and customization ### Advanced - [Enterprise Security](https://www.whisper.security/docs/integrations/splunk/es-integration.md) — Threat intel framework, risk-based alerting, correlation searches - [Investigation Macros](https://www.whisper.security/docs/integrations/splunk/macros.md) — One-click investigation shortcuts - [Cypher Reference](https://www.whisper.security/docs/integrations/splunk/cypher-reference.md) — Query syntax reference for Splunk users - [CIM Mapping](https://www.whisper.security/docs/integrations/splunk/cim-mapping.md) — Common Information Model field mapping - [Source Types](https://www.whisper.security/docs/integrations/splunk/source-types.md) — Event types and source type reference - [Use Cases](https://www.whisper.security/docs/integrations/splunk/use-cases.md) — Real-world workflows and examples ### Reference - [Troubleshooting](https://www.whisper.security/docs/integrations/splunk/troubleshooting.md) — Common issues and fixes --- ### Requirements Markdown: https://www.whisper.security/docs/integrations/splunk/requirements.md HTML: https://www.whisper.security/docs/integrations/splunk/requirements ## Software requirements ### Splunk platform | Component | Minimum version | Recommended | |-----------|----------------|-------------| | Splunk Enterprise | 10.2.0 | Latest 10.x | | Splunk Cloud | Victoria Experience | Victoria Experience | | Splunk Enterprise Security | 7.0 (optional) | Latest | ### Python The add-on requires **Python 3.13**, which Splunk Enterprise 10.2 and Splunk Cloud Platform 10.2 ship as an opt-in interpreter. Every extension point (`commands.conf`, `inputs.conf`, `restmap.conf`, `alert_actions.conf`, `app.conf`) declares `python.required = 3.13`, so Splunk selects the 3.13 interpreter automatically. ## Network requirements ### Outbound connectivity The add-on requires HTTPS (port 443) access to the Whisper Security API: | Endpoint | Protocol | Port | Purpose | |----------|----------|------|---------| | `graph.whisper.security` | HTTPS | 443 | Knowledge Graph API | > **Proxy support:** > If your Splunk server does not have direct internet access, configure an HTTP/HTTPS/SOCKS5 proxy in **Configuration > Settings > Proxy URL**. ### Firewall rules Allow outbound HTTPS (TCP 443) from: - **Search heads** -- for search commands (`whisperlookup`, `whisperquery`, `whisperschema`) - **Search heads** -- for modular inputs (baseline, threat intel, watchlist) No inbound connectivity is required. The add-on does not open any listening ports. ## Index prerequisite The add-on writes events to a Splunk index named `whisper`. **You must create this index before enabling the modular inputs.** The TA does not ship an `indexes.conf` because Splunk Cloud Victoria Experience prohibits app-shipped index definitions. | Deployment | Index creation method | |------------|----------------------| | Splunk Cloud Victoria | ACS API or Splunk Cloud Console (admin role required) | | Splunk Cloud Classic | Splunk Web -> Settings -> Indexes (admin role required) | | Splunk Enterprise | Splunk Web, CLI (`splunk add index whisper`), or cluster master `indexes.conf` | See [Installation -> Create the whisper index](https://www.whisper.security/docs/integrations/splunk/installation#create-the-whisper-index) for step-by-step instructions for each deployment type. ## API key requirements For per-plan API capability and quotas, see the [pricing page](https://www.whisper.security/pricing). Get a free API key at [console.whisper.security](https://console.whisper.security/). > **Start without a key:** > You can install and test the add-on without an API key using the Anonymous plan. The `whisperlookup` and `whisperquery` commands work with Anonymous access, but some macros (`whisper_cname_chain`, `whisper_spf_chain`) require higher plan tiers due to traversal depth requirements. ## Splunk Cloud requirements The add-on passes Splunk AppInspect with zero failures for both `precert` and `cloud` tag sets. Cloud-specific requirements that are already met: - All credentials stored via `storage/passwords` (encrypted) - No hardcoded file paths (uses `$SPLUNK_HOME` environment variable) - No prohibited `.conf` files (`outputs.conf`, `authentication.conf`, etc.) - No reserved port usage - No shebang lines in Python files - No `exec()`, `eval()`, or shell execution - Uses `sc_admin` role (not `admin`) for Cloud compatibility - SSL/TLS verification enabled on all network calls ## Next steps - [Installation](https://www.whisper.security/docs/integrations/splunk/installation.md) -- Install the add-on - [Configuration](https://www.whisper.security/docs/integrations/splunk/configuration.md) -- Set up your API key and connection settings --- ### Installation Markdown: https://www.whisper.security/docs/integrations/splunk/installation.md HTML: https://www.whisper.security/docs/integrations/splunk/installation ## Prerequisites - Splunk Enterprise 10.2+ or Splunk Cloud (Victoria Experience or Classic Experience) - Python 3.13 (included with Splunk 10.2+ as an opt-in interpreter) - A Whisper Security API key See the [Requirements](https://www.whisper.security/docs/integrations/splunk/requirements.md) page for detailed software and network requirements. > **Enterprise Deployments:** > For detailed guidance on deployment server, SHC deployer, indexer clusters, forwarder compatibility, and Splunk Cloud (Victoria vs Classic), see the [Deployment Architecture](https://www.whisper.security/docs/integrations/splunk/deployment-architecture.md) guide. ## Deployment topologies The add-on supports three deployment topologies. Install the TA on the **search head** in all cases -- search commands and modular inputs run on the search head. ### Single-instance deployment ![Diagram](https://whisper.cdn.prismic.io/whisper/afJ9SsBOoF08xdEm_splunk-installation-diagram-0.svg) Install the TA on the single Splunk instance. All components run on the same machine. ### Distributed deployment ![Diagram](https://whisper.cdn.prismic.io/whisper/afJ9S8BOoF08xdEo_splunk-installation-diagram-1.svg) Install the TA on the search head only. Indexers receive indexed events through the normal Splunk data pipeline. No TA installation is needed on indexers or forwarders. ### Splunk Cloud ![Diagram](https://whisper.cdn.prismic.io/whisper/afJ9TMBOoF08xdEp_splunk-installation-diagram-2.svg) Install the TA through self-service app installation or work with Splunk Cloud Support. The TA passes AppInspect cloud vetting with zero failures. > **Search head cluster:** > For search head cluster (SHC) deployments, deploy the TA to all cluster members via the deployer. KV Store collections replicate automatically across cluster members. ## Install from Splunkbase The add-on is published on Splunkbase: **[Whisper Security TA on Splunkbase](https://splunkbase.splunk.com/app/8695)**. **Option 1 — From Splunkbase (recommended):** 1. Download the latest release from [splunkbase.splunk.com/app/8695](https://splunkbase.splunk.com/app/8695). 2. In Splunk Web, navigate to **Apps > Manage Apps > Install app from file** and upload the `.tgz`. 3. Restart Splunk if prompted. **Option 2 — From Splunk Web:** 1. Navigate to **Apps > Find More Apps** in Splunk Web. 2. Search for "Whisper Security". 3. Click **Install**. ## Create the `whisper` index The add-on writes events to a Splunk index named `whisper`. This index must exist **before** you enable the modular inputs. The TA does not ship an `indexes.conf` because Splunk Cloud Victoria Experience prohibits app-shipped index definitions -- index creation is the deployment administrator's responsibility. The default index name is `whisper`, but you can override it via the `whisper_index` macro (see [macros documentation](https://www.whisper.security/docs/integrations/splunk/macros.md)) and update each modular input to write to a different index if required. ### Splunk Cloud Victoria Experience Use the Admin Config Service (ACS) API or the Splunk Cloud Console to create the index: **Via Splunk Cloud Console (recommended):** 1. Log in as a Cloud administrator (`sc_admin` role). 2. Navigate to **Settings > Indexes**. 3. Click **New Index**. 4. Enter: - **Index name:** `whisper` - **Index data type:** Events - **Searchable retention (days):** 180 (6 months) or per your retention policy 5. Click **Save**. **Via ACS API:** ```bash curl -X POST https://admin.splunk.com//adminconfig/v2/indexes \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "whisper", "datatype": "event", "searchableDays": 180 }' ``` ### Splunk Cloud Classic Experience 1. Log in as a Cloud administrator. 2. Navigate to **Settings > Indexes**. 3. Click **New Index**. 4. Enter: - **Index name:** `whisper` - **Index data type:** Events 5. Click **Save**. If self-service index management is not available on your stack, file a ticket with Splunk Cloud Support. ### Splunk Enterprise / on-premises **Via Splunk Web:** 1. Navigate to **Settings > Indexes**. 2. Click **New Index**. 3. Enter `whisper` as the index name. 4. Configure paths (defaults are typically fine): - **Home path:** `$SPLUNK_DB/whisper/db` - **Cold path:** `$SPLUNK_DB/whisper/colddb` - **Thawed path:** `$SPLUNK_DB/whisper/thaweddb` 5. Optional: set **Frozen time period** to `15552000` (180 days) or your retention policy. 6. Click **Save**. **Via CLI:** ```bash $SPLUNK_HOME/bin/splunk add index whisper \ -homePath '$SPLUNK_DB/whisper/db' \ -coldPath '$SPLUNK_DB/whisper/colddb' \ -thawedPath '$SPLUNK_DB/whisper/thaweddb' \ -frozenTimePeriodInSecs 15552000 ``` **Via `indexes.conf` (indexer cluster):** For indexer clusters, define the index in your cluster master's `master-apps/_cluster/local/indexes.conf` (not in the TA package): ```ini [whisper] homePath = $SPLUNK_DB/whisper/db coldPath = $SPLUNK_DB/whisper/colddb thawedPath = $SPLUNK_DB/whisper/thaweddb frozenTimePeriodInSecs = 15552000 repFactor = auto ``` Then push the bundle: `splunk apply cluster-bundle`. ### Verify the index exists After creation, confirm the index is available: ```spl | rest /services/data/indexes | search title="whisper" | table title currentDBSizeMB maxTotalDataSizeMB ``` You should see one row with `title=whisper`. If empty, the index does not exist and modular inputs will fail with `IndexProcessor - cooked index=whisper not found` errors in `splunkd.log`. ## Verify installation After installation, verify the app is visible and enabled: ```spl | rest /services/apps/local/TA-whisper-graph | table label version disabled ``` Verify search commands are registered: ```spl | whisperquery query="RETURN 1 AS test LIMIT 1" ``` ## Next steps After installation, proceed to [Configuration](https://www.whisper.security/docs/integrations/splunk/configuration.md) to set up your API key. --- ### Deployment Architecture Markdown: https://www.whisper.security/docs/integrations/splunk/deployment-architecture.md HTML: https://www.whisper.security/docs/integrations/splunk/deployment-architecture This guide covers enterprise deployment patterns for the Whisper Security Add-on, including tier placement, deployment server workflows, Search Head Cluster (SHC) configuration, indexer cluster considerations, forwarder compatibility, and Splunk Cloud specifics. ## Tier Component Matrix The TA runs entirely on search heads. Indexers and forwarders require no additional configuration. | Component | Search Head | Indexer | Forwarder | |---|---|---|---| | Search commands (whisperlookup, whisperquery) | YES | NO | NO | | Modular inputs (health, baseline, threat intel, watchlist) | YES | NO | NO | | KV Store collections (enrichment cache, threat intel) | YES | NO | NO | | API calls (outbound HTTPS to Whisper API) | YES | NO | NO | | Alert actions (Enrich with Whisper) | YES | NO | NO | | Dashboards and views | YES | NO | NO | | Saved searches (correlation, KV Store population) | YES | NO | NO | | props.conf / transforms.conf | YES | YES* | NO | | `whisper` index (admin-created, not shipped) | N/A | YES | NO | *Only if field extractions are needed at index time. For most deployments, search-time extraction (default) is sufficient. > **The TA does not ship `indexes.conf`:** > Splunk Cloud Victoria Experience prohibits app-shipped index definitions. The deployment administrator must create the `whisper` index before enabling modular inputs. See [Installation -> Create the whisper index](https://www.whisper.security/docs/integrations/splunk/installation#create-the-whisper-index) for step-by-step instructions for Cloud Victoria, Cloud Classic, and Splunk Enterprise. ## API Call Origin All outbound API calls to the Whisper Knowledge Graph originate exclusively from search heads. Ensure your firewall allows outbound HTTPS (port 443) from search heads to `graph.whisper.security` (or your configured API base URL). No indexers, forwarders, or other Splunk components make API calls. ## Deployment Server To deploy the TA via Deployment Server to search heads: 1. Place the TA in the deployment apps directory: ``` $SPLUNK_HOME/etc/deployment-apps/TA-whisper-graph/ ``` 2. Configure `serverclass.conf` to target search heads only: ```ini [serverClass:whisper_security] whitelist.0 = search-head-*.example.com [serverClass:whisper_security:app:TA-whisper-graph] restartSplunkd = true ``` 3. Push the deployment: ```bash splunk reload deploy-server ``` Do not deploy to indexers or forwarders -- the TA is not needed there. ## Search Head Cluster (SHC) ### Deploying via SHC Deployer 1. Place the TA on the deployer: ``` $SPLUNK_HOME/etc/shcluster/apps/TA-whisper-graph/ ``` 2. Push the bundle: ```bash splunk apply shcluster-bundle -target https://:8089 --answer-yes ``` ### KV Store Replication KV Store collections automatically replicate across SHC members. No additional configuration is needed. The TA's `server.conf` includes: ```ini [shclustering] conf_replication_include.ta_whisper_graph_settings = true ``` To verify KV Store replication status: ```spl | rest /services/kvstore/status ``` ### Modular Input Behavior Modular inputs run on all SHC members by default. For inputs that should run on only one member (to avoid duplicate data collection), configure the captain to manage input scheduling, or disable inputs on non-captain members. ## Indexer Cluster No TA installation is required on indexers. The TA runs entirely on search heads. If you want the `whisper` index on your indexer cluster, create your own `indexes.conf` in a cluster-master apps bundle (the TA does not ship one — Splunk Cloud Victoria forbids it): ``` $SPLUNK_HOME/etc/manager-apps/whisper-indexes/default/indexes.conf ``` ```ini [whisper] homePath = $SPLUNK_DB/whisper/db coldPath = $SPLUNK_DB/whisper/colddb thawedPath = $SPLUNK_DB/whisper/thaweddb frozenTimePeriodInSecs = 15552000 repFactor = auto ``` Then push the bundle: `splunk apply cluster-bundle`. The `repFactor = auto` setting ensures proper replication across indexer cluster peers. ## Forwarder Compatibility The TA does not run on universal or heavy forwarders. All data collection is performed by modular inputs running on the search head, which call the Whisper API directly. Forwarders can send raw network events (e.g., proxy logs, firewall logs) to indexers. Those events can then be enriched at search time using `whisperlookup` on the search head. ## Splunk Cloud ### Victoria Experience - The TA installs on the search head via the Splunk Cloud self-service app install - Modular inputs run locally on the search head -- full compatibility - KV Store is accessible from the search head - All features work without additional configuration ### Classic Experience (IDM) In Classic Experience, modular inputs run on the **Inputs Data Manager (IDM)**, a separate Splunk instance that does not have access to KV Store. The TA uses an **event-based architecture** to support this deployment: 1. **Modular inputs** (`whisper_threat_intel`, `whisper_watchlist`) write enrichment data as events to the `whisper` index instead of writing directly to KV Store 2. **Saved searches** (disabled by default) read these events and populate KV Store collections on the search head via `outputlookup` To enable the event-based pipeline on Classic Experience: 1. Enable the modular inputs via the TA configuration page 2. Enable the following saved searches: - **Whisper - Populate IP Threat Intel KV Store** - **Whisper - Populate Domain Threat Intel KV Store** - **Whisper - Populate Precomputed Enrichment KV Store** 3. Configure the saved search schedules to run after the input collection intervals ### Firewall Requirements Allow outbound HTTPS (port 443) from search heads to: - `graph.whisper.security` (production API) - Or your configured API base URL ### AppInspect Cloud Vetting The TA passes AppInspect with `cloud`, `private_victoria`, and `private_classic` tags with zero failures. ## Cloud Compatibility The TA runs identically on Splunk Enterprise, Splunk Cloud Classic, and Splunk Cloud Victoria. Feature coverage is the same across all three platforms; only the management plane and modular-input host differ. ### Capability matrix | Capability | Enterprise | Cloud Classic | Cloud Victoria | |------------|:----------:|:-------------:|:--------------:| | Search commands (`whisperlookup`, `whisperquery`, `whisperschema`, `whisperevict`, `whisperflush`) | Yes | Yes | Yes | | Modular inputs (baseline, threat intel, watchlist) | Yes | Yes (on IDM or heavy forwarder) | Yes (on search head) | | KV Store collections (`whisper_*`) | Yes | Yes | Yes | | API key in `storage/passwords` | Yes | Yes | Yes | | Adaptive response action (`whisper_enrich`) | Yes | Yes | Yes | | CIM field aliases on `whisper:enrichment` | Yes | Yes | Yes | | Example enrichment templates (`savedsearches.conf`) | Yes | Yes | Yes | | Investigation macros | Yes | Yes | Yes | | Dashboard Studio dashboards | Yes | Yes | Yes | | Custom role (`whisper_user`) | Yes | Yes | Yes | | Ships an `indexes.conf` | No (admin creates the `whisper` index) | No | No (admin creates via ACS) | ### Cloud Classic vs Cloud Victoria | Area | Cloud Classic | Cloud Victoria | |------|---------------|----------------| | App install | Splunk-managed, ticket-based | ACS-managed, self-service | | Index creation | Splunk-managed, ticket-based | ACS API or Victoria UI | | Modular input host | IDM or heavy forwarder | Search head | | Upgrade cadence | Coordinated with Splunk | Self-service via ACS | ### Anonymous-tier API key If you use an Anonymous-tier API key (or no key at all), graph traversal is capped at 2 hops. The investigation macros that traverse deeper (`whisper_cname_chain` up to 5 hops, `whisper_spf_chain` up to 3 hops) will return `QueryDepthExceeded` errors until you upgrade to a Free or Professional plan. ## Configuration File Distribution | Config File | Search Head | Indexer | Notes | |---|---|---|---| | `app.conf` | YES | NO | App identity and triggers | | `commands.conf` | YES | NO | Custom search commands | | `collections.conf` | YES | NO | KV Store schemas | | `transforms.conf` | YES | YES* | Lookup definitions | | `props.conf` | YES | YES* | Field extractions | | `savedsearches.conf` | YES | NO | Correlation and population searches | | `macros.conf` | YES | NO | Investigation macros | | `indexes.conf` (admin-supplied) | NO | YES | Index definitions (NOT shipped — admin creates via ACS/CLI/UI) | | `authorize.conf` | YES | NO | Custom roles | *Only needed on indexers if index-time field extractions are configured. ## Troubleshooting ### Inputs Not Running (SHC) If modular inputs are not collecting data on an SHC: 1. Verify the TA is deployed on all SHC members 2. Check that inputs are enabled: **Settings > Data Inputs > Whisper** 3. Verify API connectivity: run `| whisperquery query="CALL whisper.quota()"` from the search bar ### Inputs Not Running (Splunk Cloud Classic) If using Classic Experience with IDM: 1. Verify the event-based saved searches are enabled 2. Check that events are being written: search for `index=whisper sourcetype=whisper:threat_intel` 3. If events exist but KV Store is empty, verify the saved searches are running on schedule ### KV Store Replication Issues (SHC) Verify replication is working: ```spl | rest /services/kvstore/status | table title, currentStatus, replicationStatus ``` If collections are not replicating, verify `server.conf` includes the replication settings. --- ### Configuration Markdown: https://www.whisper.security/docs/integrations/splunk/configuration.md HTML: https://www.whisper.security/docs/integrations/splunk/configuration All settings are managed through the Splunk Web UI via the UCC Framework. ![Diagram](https://whisper.cdn.prismic.io/whisper/afJ9ScBOoF08xdEl_splunk-configuration-diagram-0.svg) ## Get an API key 1. Go to [console.whisper.security](https://console.whisper.security/) and create a free account 2. Once logged in, generate an API key from the console dashboard 3. Copy the key -- you will use it in the next step ## Account setup 1. In Splunk Web, navigate to **Apps > Whisper Security TA > Configuration > Account** 3. Click **Add** to create a new account 4. Enter an account name (e.g., `production`), the API base URL, and your API key 5. Click **Save** -- the API key is stored encrypted via Splunk `storage/passwords` ### Account fields | Field | Required | Default | Description | |-------|----------|---------|-------------| | Account Name | Yes | -- | Unique identifier for this account | | Base URL | Yes | `https://graph.whisper.security` | Whisper API base URL | | API Key | Yes | -- | Whisper API Key | ## Connection settings Navigate to **Configuration > Settings** to configure connection parameters: | Field | Default | Range | Description | |-------|---------|-------|-------------| | Request Timeout (seconds) | 120 | 5-300 | HTTP request timeout for API calls | | Proxy URL | -- | -- | Optional HTTP/HTTPS/SOCKS5 proxy URL | ### Proxy configuration If your Splunk server does not have direct internet access, configure a proxy: | Proxy type | URL format | Example | |-----------|-----------|---------| | HTTP proxy | `http://host:port` | `http://proxy.internal:8080` | | HTTPS proxy | `https://host:port` | `https://proxy.internal:8443` | | SOCKS5 proxy | `socks5://host:port` | `socks5://proxy.internal:1080` | | Authenticated proxy | `http://user:pass@host:port` | `http://admin:secret@proxy.internal:8080` | > **Proxy authentication:** > If your proxy requires authentication, include the credentials in the URL. The proxy URL is stored in Splunk's configuration system (not `storage/passwords`), so use a service account with minimal privileges. ### SSL/TLS All API communication with `graph.whisper.security` uses HTTPS with a valid certificate. SSL certificate verification is always enabled and is not configurable. ## Logging The add-on writes logs to `$SPLUNK_HOME/var/log/splunk/`. Logs are automatically indexed into the `_internal` index. ### Log locations All components log to a single shared log file: | Component | Log file | Search | |-----------|----------|--------| | All (commands, inputs, REST handlers) | `ta_whisper_graph.log` | `index=_internal source=*ta_whisper_graph.log` | ### Log levels Log verbosity is controlled by Splunk's standard logging configuration. To increase the log level for debugging: 1. Navigate to **Settings > Server Settings > Server Logging** 2. Search for `whisper` 3. Set the desired log level (DEBUG, INFO, WARNING, ERROR) > **Debug logging:** > Enable DEBUG logging temporarily to diagnose API connectivity or enrichment issues. Remember to set it back to INFO when done -- DEBUG logging generates significant volume. ### Searching logs ```spl index=_internal source=*ta_whisper_graph.log | table _time log_level _raw | sort -_time ``` ## Verify it works After setting up your account, check that the connection works: ```spl | whisperquery query="RETURN 1 AS test LIMIT 1" ``` If you get a result back, you are connected. Try an enrichment: ```spl | makeresults | eval dest_host="example.com" | whisperlookup field=dest_host type=domain | table dest_host whisper_ip whisper_asn whisper_asn_name ``` ## Next steps - [Search Commands](https://www.whisper.security/docs/integrations/splunk/search-commands.md) -- Full command reference - [Enrichment](https://www.whisper.security/docs/integrations/splunk/enrichment.md) -- Enrichment pipeline details - [Macros](https://www.whisper.security/docs/integrations/splunk/macros.md) -- Pre-built investigation macros - [ES Integration](https://www.whisper.security/docs/integrations/splunk/es-integration.md) -- Threat intel and correlation searches - [Use Cases](https://www.whisper.security/docs/integrations/splunk/use-cases.md) -- Security workflow examples --- ### Search Commands Markdown: https://www.whisper.security/docs/integrations/splunk/search-commands.md HTML: https://www.whisper.security/docs/integrations/splunk/search-commands ![Diagram](https://whisper.cdn.prismic.io/whisper/afJjd8BOoF08xcsq_splunk-search-commands-diagram-0.svg) | | | |---|---| | **whisperquery** | Raw Cypher queries against the Knowledge Graph | | **whisperschema** | Inspect the Knowledge Graph schema (node labels, relationship types, properties) | | **whisperlookup** | Inline enrichment of events in SPL | | **whisperevict / whisperflush** | Cache management | ## whisperlookup Streaming command that enriches events with IOC context from the Knowledge Graph. It appends enrichment fields to each event inline. ### Syntax ```spl | whisperlookup field= [type=auto|domain|ip] [include_threat_intel=true|false] [include_cname=true|false] [include_nameserver=true|false] [include_feeds=true|false] [add_prefix=] [use_cache=true|false] ``` ### Parameters | Parameter | Required | Default | Description | |-----------|----------|---------|-------------| | `field` | Yes | — | Event field containing the indicator to enrich | | `type` | No | `auto` | Indicator type: `auto` (detect from value), `domain`, or `ip` | | `include_threat_intel` | No | `true` | Include all threat-related fields (threat score, level, boolean indicators, ASN threat data, and explain API results) | | `include_cname` | No | `true` | Include CNAME chain resolution | | `include_nameserver` | No | `true` | Include nameserver information | | `include_feeds` | No | `true` | Include threat feed listings | | `add_prefix` | No | `whisper_` | Prefix added to enrichment field names | | `use_cache` | No | `true` | When `false`, bypasses the KV Store enrichment cache and precomputed collection for this search (always calls the live API) | ### Output fields For domain enrichment, these fields are appended (with the configured prefix): | Field | Description | Example | |-------|-------------|---------| | `whisper_ip` | Resolved IP address(es) | `93.184.216.34` | | `whisper_prefix` | BGP prefix (CIDR block) | `93.184.216.0/24` | | `whisper_asn` | Autonomous System Number | `AS15133` | | `whisper_asn_name` | ASN organization name | `Edgecast Inc.` | | `whisper_country` | Country code | `US` | | `whisper_cohost_count` | Number of co-hosted domains | `42` | | `whisper_cname_chain` | CNAME alias chain | `www.example.com,example.com` | | `whisper_cname_depth` | CNAME chain depth | `1` | | `whisper_cname_target` | Final CNAME target | `example.com` | | `whisper_nameservers` | Nameserver list | `a.iana-servers.net,b.iana-servers.net` | | `whisper_threat_score` | Threat score (unbounded float, 0-100+) | `15` | | `whisper_threat_level` | Threat level (from API or derived) | `LOW` | | `whisper_is_threat` | Boolean: known threat indicator | `true` | | `whisper_is_tor` | Boolean: Tor exit node | `false` | | `whisper_is_c2` | Boolean: command-and-control | `false` | | `whisper_is_malware` | Boolean: malware distribution | `false` | | `whisper_is_phishing` | Boolean: phishing host | `false` | | `whisper_is_spam` | Boolean: spam source | `false` | | `whisper_is_bruteforce` | Boolean: brute-force source | `false` | | `whisper_is_scanner` | Boolean: network scanner | `false` | | `whisper_is_blacklist` | Boolean: on public blacklist | `false` | | `whisper_is_proxy` | Boolean: open proxy | `false` | | `whisper_is_vpn` | Boolean: VPN exit node | `false` | | `whisper_is_anonymizer` | Boolean: anonymization service | `false` | | `whisper_is_whitelist` | Boolean: explicitly whitelisted | `false` | | `whisper_threat_explanation` | Human-readable threat summary | `Low risk indicator...` | | `whisper_threat_factors` | Contributing factors (multivalue) | `listed_in_blocklist_de` | | `whisper_threat_sources` | Per-feed source data | (structured list) | | `whisper_threat_feed_ids` | Feed IDs for ES threat_key | `blocklist_de,spamhaus` | | `whisper_threat_first_seen` | Earliest feed observation date | `2024-01-15` | | `whisper_threat_last_seen` | Most recent feed observation date | `2024-06-30` | | `whisper_feed_names` | Threat feed names | `blocklist_de,spamhaus` | | `whisper_feed_count` | Number of feeds listing this indicator | `2` | | `whisper_feed_categories` | Feed categories (from CATEGORY nodes) | `Malware,Spam` | | `whisper_type` | Indicator type used | `domain` | | `whisper_risk_score` | Normalized risk score (0-100), maps to ES `risk_score` | `45` | | `whisper_risk_level` | Risk level: informational, low, medium, high, critical | `medium` | | `whisper_risk_factors_list` | Comma-separated contributing risk factor names | `suspicious_spf, threat_feed_medium` | | `whisper_risk_components` | JSON per-factor score breakdown | `{"suspicious_spf": {"points": 20, ...}}` | The inline threat fields also include: | Field | Description | Example | |-------|-------------|---------| | `whisper_threat_sources_count` | Number of threat intelligence sources listing this indicator | `3` | | `whisper_threat_first_seen` | Earliest date this indicator appeared in any feed | `2024-01-15` | | `whisper_threat_last_seen` | Most recent date this indicator appeared in any feed | `2024-06-30` | For IP enrichment, the same threat fields apply plus: | Field | Description | Example | |-------|-------------|---------| | `whisper_reverse_dns_count` | Number of reverse DNS hostnames | `5` | | `whisper_asn_threat_level` | ASN overall threat level: NONE/LOW/MEDIUM/HIGH/CRITICAL | `MEDIUM` | | `whisper_asn_threat_score` | ASN composite threat score (numeric) | `45.2` | | `whisper_asn_max_threat_score` | Highest single-prefix threat score within the ASN | `82.0` | | `whisper_asn_avg_threat_score` | Average threat score across the ASN's prefixes | `31.5` | | `whisper_asn_has_threatening_prefixes` | Boolean: ASN contains at least one high-risk prefix | `true` | > **Inline threat data:** > `whisper_threat_score` and the boolean `whisper_is_*` fields come from inline properties on the IPV4 node in the graph. The `explain()` API is only called when those inline properties are absent. It returns explanation text, per-feed sources, and first/last seen dates. Both paths populate `whisper_threat_score` and `whisper_threat_level`. CIM field aliases are also created: | CIM Field | Source | |-----------|--------| | `dest_ip` | `whisper_ip` | | `dest_country` | `whisper_country` | | `dest_asn` | `whisper_asn` | | `threat_score` | `whisper_threat_score` | | `threat_level` | `whisper_threat_level` | | `risk_score` | `whisper_risk_score` | | `risk_level` | `whisper_risk_level` | | `is_threat` | `whisper_is_threat` | | `is_c2` | `whisper_is_c2` | | `is_tor` | `whisper_is_tor` | | `is_malware` | `whisper_is_malware` | | `is_phishing` | `whisper_is_phishing` | | `is_anonymizer` | `whisper_is_anonymizer` | | `is_spam` | `whisper_is_spam` | | `is_bruteforce` | `whisper_is_bruteforce` | | `is_scanner` | `whisper_is_scanner` | | `is_blacklist` | `whisper_is_blacklist` | | `is_proxy` | `whisper_is_proxy` | | `is_vpn` | `whisper_is_vpn` | | `is_whitelist` | `whisper_is_whitelist` | ### Examples **Enrich firewall logs with domain context:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_host type=domain | where whisper_threat_score > 50 | table _time dest_host whisper_asn_name whisper_threat_level whisper_feed_names ``` **Enrich IP addresses with auto-detection:** ```spl index=proxy sourcetype=squid | whisperlookup field=src_ip | stats count by whisper_asn_name whisper_country ``` **Domain enrichment without threat intel (faster):** ```spl index=dns sourcetype=dns | whisperlookup field=query type=domain include_threat_intel=false include_feeds=false | table query whisper_ip whisper_asn whisper_asn_name whisper_cohost_count ``` **Custom field prefix:** ```spl index=web sourcetype=access_combined | whisperlookup field=clientip type=ip add_prefix="w_" | table clientip w_asn_name w_country w_threat_level ``` > **Private IP addresses:** > Private IP addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) are automatically skipped — no API call is made and no enrichment fields are added. > **Caching:** > Results are automatically cached in the `whisper_enrichment_cache` KV Store collection. Subsequent lookups for the same indicator within the TTL window (default: 1 hour) are served from cache without an API call. Use `| whisperflush` to clear the cache. --- ## whisperquery Generating command that runs ad-hoc Cypher queries against the Knowledge Graph. ### Syntax ```spl | whisperquery query="" [params=""] [params_b64=""] [max_results=] [validate_indicator=""] ``` ### Parameters | Parameter | Required | Default | Description | |-----------|----------|---------|-------------| | `query` | Yes | — | Cypher query string (must include LIMIT clause) | | `params` | No | — | Query parameters as `key=value,key2=value2` or JSON string | | `params_b64` | No | — | Base64-encoded JSON parameters (avoids SPL quote-escaping issues with arrays) | | `max_results` | No | `10000` | Maximum number of results to return | | `validate_indicator` | No | — | When set to a parameter name (e.g. `indicator`), applies the strict allowlist `^[A-Za-z0-9._:\-]+$` to `parameters[]`, lowercases it, and short-circuits with zero events on missing/empty/invalid input before executing any Cypher. Use this in dashboards where the parameter value originates from user input (e.g. `$indicator_input$`) — the Investigation dashboard uses `validate_indicator="indicator"` on every pivot panel to replace the legacy SPL `\| where match(...)` guard. | ### Examples **Look up a domain's infrastructure:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:RESOLVES_TO]->(ip:IPV4)-[:BELONGS_TO]->(p:PREFIX)<-[:ROUTES]-(a:ASN)-[:HAS_NAME]->(n:ASN_NAME) RETURN h.name AS hostname, ip.name AS ip, p.name AS prefix, a.name AS asn, n.name AS asn_name LIMIT 10" params="domain=example.com" ``` **Find co-hosted domains:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:RESOLVES_TO]->(ip:IPV4)<-[:RESOLVES_TO]-(cohost:HOSTNAME) WHERE cohost.name <> $domain RETURN ip.name AS ip, cohost.name AS cohost LIMIT 100" params="domain=example.com" ``` **Get ASN routing information:** ```spl | whisperquery query="MATCH (a:ASN {name: $asn})-[:ROUTES]->(p:PREFIX) RETURN a.name AS asn, p.name AS prefix LIMIT 200" params="asn=AS13335" ``` **Use JSON parameters:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain}) RETURN h LIMIT 1" params='{"domain": "example.com"}' ``` > **Write operations are blocked:** > Queries containing `CREATE`, `DELETE`, `SET`, `MERGE`, `DROP`, `REMOVE`, or `DETACH` keywords are rejected before being sent to the API. The Knowledge Graph is read-only. --- ## whisperschema Generating command that shows the Knowledge Graph schema -- node labels, relationship types, property keys, and metadata (descriptions, examples, counts, query patterns). ### Syntax ```spl | whisperschema mode= ``` ### Parameters | Parameter | Required | Default | Description | |-----------|----------|---------|-------------| | `mode` | No | `labels` | Schema query mode | ### Modes | Mode | Description | Cypher Equivalent | |------|-------------|-------------------| | `labels` | List all node labels | `CALL db.labels()` | | `relationships` | List all relationship types | `CALL db.relationshipTypes()` | | `properties` | List all property keys in the graph | `CALL db.propertyKeys()` | | `schema` | Schema with descriptions, examples, counts, fast/slow patterns | `CALL db.schema()` | | `full` | Combined schema + property keys | `CALL db.schema()` + `CALL db.propertyKeys()` | All events include a `whisper_schema_mode` field for filtering. ### Schema mode output fields When using `mode=schema` or `mode=full`, each event includes all fields listed below. Fields that do not apply to a particular event type (e.g., `sourceLabels` on a node event) are present as empty strings. This ensures all events have a uniform field set, which is required by the Splunk chunked v2 protocol for correct field display. | Field | Description | |-------|-------------| | `type` | Entity type: `node`, `relationship`, or `tips` | | `name` | Label or relationship name (e.g., `HOSTNAME`, `RESOLVES_TO`) | | `count` | Number of entities in the graph | | `description` | Human-readable description | | `example` | Example value | | `sourceLabels` | Source node labels (populated for relationships, JSON array) | | `targetLabels` | Target node labels (populated for relationships, JSON array) | | `fastPatterns` | Efficient query patterns (populated for relationships and tips, JSON array) | | `slowPatterns` | Patterns to avoid (populated for relationships and tips, JSON array) | | `bestPractices` | Query best practices (populated for relationships and tips, JSON array) | ### Examples **List all node types in the graph:** ```spl | whisperschema mode=labels ``` **List all relationship types:** ```spl | whisperschema mode=relationships ``` **List all property keys:** ```spl | whisperschema mode=properties ``` **Explore the full schema with descriptions and examples:** ```spl | whisperschema mode=schema | search type=node | table name, count, description, example ``` **View query best practices:** ```spl | whisperschema mode=schema | search type=tips | table bestPractices, fastPatterns, slowPatterns ``` **Get combined schema and property keys:** ```spl | whisperschema mode=full | stats count by whisper_schema_mode ``` --- ## whisperflush Generating command that flushes the enrichment cache. Requires `admin` or `sc_admin` role. ### Syntax ```spl | whisperflush [collection=cache|precomputed|all] ``` ### Parameters | Parameter | Required | Default | Description | |-----------|----------|---------|-------------| | `collection` | No | `cache` | Which collection to flush | ### Targets | Collection | KV Store | Description | |------------|----------|-------------| | `cache` | `whisper_enrichment_cache` | TTL-based enrichment cache | | `precomputed` | `whisper_precomputed_enrichment` | Pre-computed watchlist enrichments | | `all` | Both | Flush both cache and precomputed collections | ### Examples **Flush the enrichment cache:** ```spl | whisperflush ``` **Flush pre-computed enrichments:** ```spl | whisperflush collection=precomputed ``` **Flush everything:** ```spl | whisperflush collection=all ``` --- ## whisperevict Generating command that evicts expired entries from the enrichment cache using the KV Store REST API. More efficient than loading all records through the SPL pipeline, especially for large caches. This command is used by the "Whisper - Evict Expired Cache Entries" saved search. ### Syntax ```spl | whisperevict ``` ### How it works The command uses two phases: 1. **Bulk delete:** Queries the KV Store for expired entries with the default TTL and deletes them in a single REST API call. 2. **Custom TTL:** Scans entries with non-default TTLs and deletes expired ones individually. This avoids loading all cache records into the search pipeline, which matters for large caches. ### Output fields | Field | Description | |-------|-------------| | `collection` | The KV Store collection name | | `action` | Always `evict_expired` | | `status` | `success`, `skipped`, or `error` | | `evicted` | Number of entries removed | | `ttl_seconds` | TTL used for expiration calculation | | `error` | Error message if status is `error` | ### Examples **Run cache eviction manually:** ```spl | whisperevict ``` --- ### Enrichment Pipeline Markdown: https://www.whisper.security/docs/integrations/splunk/enrichment.md HTML: https://www.whisper.security/docs/integrations/splunk/enrichment ## Overview The enrichment pipeline adds infrastructure context from the Knowledge Graph (billions of nodes, tens of billions of edges) to your security events. ![Diagram](https://whisper.cdn.prismic.io/whisper/afJjeMBOoF08xcsr_splunk-enrichment-diagram-0.svg) ## Architecture The enrichment pipeline is a streaming search command (`whisperlookup`) that processes events inline in the SPL pipeline. Each event passes through five stages: ![Diagram](https://whisper.cdn.prismic.io/whisper/afJjecBOoF08xcst_splunk-enrichment-diagram-1.svg) The pipeline is built from four modules: | Module | Role | |--------|------| | `whisper_enrichment.py` | Orchestrates the pipeline: type detection, cache check, API call, field mapping | | `whisper_enrichment_queries.py` | Builds parameterized Cypher queries for domain and IP enrichment | | `whisper_enrichment_parsers.py` | Parses API responses into flat dictionaries | | `whisper_field_mapper.py` | Maps parsed results to `whisper_` prefixed and CIM-aliased fields | All API calls go through `WhisperAPIClient`, which handles rate limiting, retries, and connection pooling. Results are cached in the `whisper_enrichment_cache` KV Store collection via `whisper_cache.py`. ## Enrichment pipeline 1. **Type detection** -- figures out if the indicator is an IP (IPv4 regex) or domain 2. **Cache check** -- looks in the `whisper_enrichment_cache` KV Store for a cached result 3. **API enrichment** -- queries the Knowledge Graph with parameterized Cypher 4. **Field mapping** -- maps graph results to CIM-compliant field names with a `whisper_` prefix 5. **Event output** -- appends the enrichment fields to the original event ## Domain enrichment Domain enrichment runs in two stages to stay within the API's 2-hop depth limit: 1. **Resolve** — maps the hostname to its IP addresses (1 hop) 2. **Infrastructure** — looks up BGP context for the first resolved IP via the `ANNOUNCED_BY` path (matching the IP enrichment path) ``` Stage 1: HOSTNAME → RESOLVES_TO → IPV4 Stage 2: IPV4 → ANNOUNCED_BY → PREFIX ← ROUTES ← ASN (then: ASN → HAS_NAME, ASN → HAS_COUNTRY as separate 1-hop queries) ``` The IP node's inline threat properties (`threatScore`, `isThreat`, `isTor`, etc.) are returned directly from Stage 2. A separate `explain()` call is only made when the inline data is absent. **Example:** ```spl index=dns sourcetype=dns | whisperlookup field=query type=domain | table query whisper_ip whisper_prefix whisper_asn whisper_asn_name whisper_country ``` **Output fields:** `whisper_ip`, `whisper_prefix`, `whisper_asn`, `whisper_asn_name`, `whisper_country`, `whisper_cohost_count`, plus inline threat fields when available (see [Threat intelligence enrichment](#threat-intelligence-enrichment)) ## IP enrichment Resolves an IP to its network context using the `ANNOUNCED_BY` path: ``` IPV4 → ANNOUNCED_BY → PREFIX ← ROUTES ← ASN → HAS_NAME → ASN_NAME ASN → HAS_COUNTRY → COUNTRY ``` Inline threat properties on the IPV4 node are returned in the same query, eliminating a separate `explain()` call when the data is present. **Example:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip type=ip | table dest_ip whisper_prefix whisper_asn whisper_asn_name whisper_country ``` **Output fields:** `whisper_prefix`, `whisper_asn`, `whisper_asn_name`, `whisper_country`, `whisper_reverse_dns_count`, `whisper_cohost_count`, inline threat fields (see below), and ASN threat reputation fields when available > **Note:** > Private IP addresses (RFC 1918) are automatically skipped. ## Threat intelligence enrichment Threat data is returned in two ways: 1. **Inline** — `threatScore`, `isThreat`, `isTor`, `isC2`, and related boolean flags are properties on IPV4 nodes in the graph. The infrastructure queries (domain and IP) return these directly. No extra API call is needed. 2. **explain()** — a richer assessment including explanation text, contributing factors, per-feed sources, and first/last seen dates. The `explain()` call is skipped when inline data is already present (`threat_score` is non-null). ```spl index=proxy sourcetype=squid | whisperlookup field=dest_host include_threat_intel=true include_feeds=true | where whisper_threat_score > 30 | table dest_host whisper_threat_level whisper_threat_score whisper_feed_names whisper_threat_explanation ``` **Output fields from inline data:** | Field | Description | |-------|-------------| | `whisper_threat_score` | Numeric threat score (0-100+, unbounded float) | | `whisper_threat_level` | NONE/INFO/LOW/MEDIUM/HIGH/CRITICAL (derived from score when API returns null) | | `whisper_is_threat` | Boolean: indicator is known threat | | `whisper_is_tor` | Boolean: Tor exit node | | `whisper_is_c2` | Boolean: command-and-control server | | `whisper_is_malware` | Boolean: malware distribution | | `whisper_is_phishing` | Boolean: phishing host | | `whisper_is_spam` | Boolean: spam source | | `whisper_is_bruteforce` | Boolean: brute-force source | | `whisper_is_scanner` | Boolean: network scanner | | `whisper_is_blacklist` | Boolean: on public blacklist | | `whisper_is_proxy` | Boolean: open proxy | | `whisper_is_vpn` | Boolean: known VPN exit | | `whisper_is_anonymizer` | Boolean: anonymization service | | `whisper_is_whitelist` | Boolean: explicitly whitelisted | | `whisper_threat_sources_count` | Number of threat intelligence sources listing this indicator | | `whisper_threat_first_seen` | Earliest date this indicator appeared in any feed | | `whisper_threat_last_seen` | Most recent date this indicator appeared in any feed | **ASN threat reputation fields** (returned with IP and domain enrichment): | Field | Description | |-------|-------------| | `whisper_asn_threat_level` | ASN overall threat level: NONE/LOW/MEDIUM/HIGH/CRITICAL | | `whisper_asn_threat_score` | ASN composite threat score (numeric) | | `whisper_asn_max_threat_score` | Highest single-prefix threat score within the ASN | | `whisper_asn_avg_threat_score` | Average threat score across the ASN's prefixes | | `whisper_asn_has_threatening_prefixes` | Boolean: ASN contains at least one high-risk prefix | > **Null-safe fields:** > ASN threat fields are only present in enrichment output when the API returns a non-null value. Use `isnotnull(whisper_asn_threat_level)` in SPL to filter only events where the API provided ASN reputation data. **Additional fields from explain() (when called):** | Field | Description | |-------|-------------| | `whisper_threat_explanation` | Human-readable threat summary | | `whisper_threat_factors` | Contributing factors (multivalue) | | `whisper_threat_sources` | Per-feed source data (list of dicts) | | `whisper_threat_feed_ids` | Feed identifiers for ES `threat_key` | | `whisper_threat_breakdown` | Component scores from the explain API | | `whisper_threat_available` | Whether threat data is available | | `whisper_threat_cached` | Whether the explain response was cached | > **Score range:** > `whisper_threat_score` is an unbounded float (typically 0-100+), not a 0-1 fraction. Thresholds: >= 50 is high confidence, >= 10 is moderate. ## WHOIS enrichment Domain enrichment automatically includes WHOIS data when available: ``` HOSTNAME → HAS_REGISTRAR → REGISTRAR HOSTNAME → REGISTERED_BY → ORGANIZATION HOSTNAME → HAS_EMAIL → EMAIL HOSTNAME → HAS_PHONE → PHONE HOSTNAME → PREV_REGISTRAR → REGISTRAR (previous) ``` **Example:** ```spl index=dns sourcetype=dns | whisperlookup field=query type=domain | table query whisper_registrar whisper_registrant_org whisper_registrant_email whisper_organization ``` **Output fields:** | Field | Description | |-------|-------------| | `whisper_registrar` | Domain registrar name | | `whisper_registrant_org` | Registrant organization | | `whisper_registrant_email` | Registrant contact email | | `whisper_registrant_phone` | Registrant phone number | | `whisper_registration_date` | Domain registration date | | `whisper_expiration_date` | Domain expiration date | | `whisper_prev_registrar` | Previous registrar (registrar change detection) | | `whisper_organization` | Registrant organization via REGISTERED_BY edge | > **Sparse WHOIS data:** > WHOIS data varies by domain. Not all fields will be populated for every domain. Fields use OPTIONAL MATCH and will be absent (not empty) when data is unavailable. ## GeoIP city-level enrichment IP enrichment automatically includes city-level geolocation: ``` IPV4 → LOCATED_IN → CITY ``` CITY nodes contain latitude, longitude, and country code embedded in the name. **Example:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip type=ip | table dest_ip whisper_geo_city whisper_geo_country whisper_geo_latitude whisper_geo_longitude ``` **Output fields:** | Field | Description | |-------|-------------| | `whisper_geo_city` | City name (e.g., "Mountain View") | | `whisper_geo_country` | Country code extracted from city name (e.g., "US") | | `whisper_geo_latitude` | City latitude (decimal degrees) | | `whisper_geo_longitude` | City longitude (decimal degrees) | > **Anycast IPs:** > Anycast IPs (e.g., 1.1.1.1) may not have a single LOCATED_IN edge. GeoIP fields will be absent for such IPs. ## HOSTNAME threat properties Domain enrichment queries threat properties directly from the HOSTNAME node, independent of any IPV4-derived threat data: **Example:** ```spl index=dns sourcetype=dns | whisperlookup field=query type=domain include_threat_intel=true | where whisper_hostname_threat_level="HIGH" OR whisper_hostname_threat_level="CRITICAL" | table query whisper_hostname_threat_score whisper_hostname_threat_level ``` **Output fields:** All fields are prefixed with `hostname_` to distinguish from IP-level threat data: | Field | Description | |-------|-------------| | `whisper_hostname_threat_score` | HOSTNAME node threat score | | `whisper_hostname_threat_level` | HOSTNAME threat level (NONE/LOW/MEDIUM/HIGH/CRITICAL) | | `whisper_hostname_is_spam` | HOSTNAME is a spam source | | `whisper_hostname_is_proxy` | HOSTNAME is a proxy | | `whisper_hostname_is_vpn` | HOSTNAME is a VPN exit | | (etc.) | All `is_*` booleans available with `hostname_` prefix | ## Prefix threat assessment IP enrichment includes threat data from ANNOUNCED_PREFIX and REGISTERED_PREFIX nodes: ``` IPV4 → ANNOUNCED_BY → ANNOUNCED_PREFIX (BGP routing) IPV4 → BELONGS_TO → REGISTERED_PREFIX (RIR allocation) ``` **Output fields:** | Field | Description | |-------|-------------| | `whisper_announced_prefix` | BGP announced prefix name | | `whisper_ap_threat_score` | Announced prefix threat score | | `whisper_ap_threat_level` | Announced prefix threat level | | `whisper_ap_is_threat` | Announced prefix is threat | | `whisper_registered_prefix` | RIR registered prefix name | | `whisper_rp_threat_score` | Registered prefix threat score | | `whisper_rp_threat_level` | Registered prefix threat level | | `whisper_rp_is_threat` | Registered prefix is threat | ## BGP hijack detection IP enrichment automatically compares the announcing ASN (BGP) with the registered ASN (RIR) to detect potential route hijacking: **Example:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip type=ip | where whisper_bgp_hijack_detected="true" | table dest_ip whisper_bgp_announcing_asn whisper_bgp_registered_asn ``` **Output fields:** | Field | Description | |-------|-------------| | `whisper_bgp_hijack_detected` | Boolean: announcing ASN differs from registered ASN | | `whisper_bgp_announcing_asn` | ASN currently announcing the prefix via BGP | | `whisper_bgp_registered_asn` | ASN registered as the prefix owner with RIR | | `whisper_bgp_announced_prefix` | The announced prefix | | `whisper_bgp_registered_prefix` | The registered prefix | > **High-impact signal:** > BGP hijack detection carries the highest risk score (70 points) in the risk scoring system. A detected hijack means the IP's traffic may be routed through an unauthorized network. ## Web link graph enrichment Domain enrichment includes web link data from the LINKS_TO relationship (billions of edges): ``` HOSTNAME → LINKS_TO → HOSTNAME (outbound) HOSTNAME ← LINKS_TO ← HOSTNAME (inbound) ``` **Example:** ```spl index=dns sourcetype=dns | whisperlookup field=query type=domain | where whisper_link_count > 0 | table query whisper_link_count whisper_outbound_links whisper_inbound_links ``` **Output fields:** | Field | Description | |-------|-------------| | `whisper_linked_domains` | Deduplicated list of all linked domains | | `whisper_link_count` | Total unique linked domains | | `whisper_suspicious_link_count` | Number of links to/from suspicious or threat-listed domains | | `whisper_outbound_links` | Domains this domain links to (up to 25) | | `whisper_inbound_links` | Domains that link to this domain (up to 25) | > **Trust signal:** > Domains with many inbound links from legitimate sites are more likely to be trustworthy. Domains with no inbound links or linked only by suspicious sites are flagged in the risk score. ## CNAME chain enrichment Follows CNAME alias chains up to 5 hops: ``` HOSTNAME -[:ALIAS_OF*1..5]-> HOSTNAME ``` **Example:** ```spl index=dns sourcetype=dns | whisperlookup field=query include_cname=true | where whisper_cname_depth > 0 | table query whisper_cname_chain whisper_cname_target whisper_cname_depth ``` **Output fields:** `whisper_cname_chain`, `whisper_cname_depth`, `whisper_cname_target` ## Nameserver enrichment Pulls nameservers for a domain: ``` HOSTNAME <-[:NAMESERVER_FOR]- HOSTNAME ``` **Example:** ```spl index=dns sourcetype=dns | whisperlookup field=query include_nameserver=true | table query whisper_nameservers ``` **Output fields:** `whisper_nameservers` (comma-separated list) ## CIM field mapping Enrichment fields are aliased to CIM-compliant names: | Whisper Field | CIM Field | CIM Data Model | |---------------|-----------|----------------| | `whisper_ip` | `dest_ip` | Network Resolution | | `whisper_country` | `dest_country` | Network Resolution | | `whisper_asn` | `dest_asn` | Network Resolution | | `whisper_threat_score` | `threat_score` | Threat Intelligence | | `whisper_threat_level` | `threat_level` | Threat Intelligence | | `whisper_risk_score` | `risk_score` | Threat Intelligence | | `whisper_risk_level` | `risk_level` | Threat Intelligence | | `whisper_is_threat` | `is_threat` | Threat Intelligence | | `whisper_is_c2` | `is_c2` | Threat Intelligence | | `whisper_is_tor` | `is_tor` | Threat Intelligence | | `whisper_is_malware` | `is_malware` | Threat Intelligence | | `whisper_is_phishing` | `is_phishing` | Threat Intelligence | | `whisper_is_anonymizer` | `is_anonymizer` | Threat Intelligence | | `whisper_is_spam` | `is_spam` | Threat Intelligence | | `whisper_is_bruteforce` | `is_bruteforce` | Threat Intelligence | | `whisper_is_scanner` | `is_scanner` | Threat Intelligence | | `whisper_is_blacklist` | `is_blacklist` | Threat Intelligence | | `whisper_is_proxy` | `is_proxy` | Threat Intelligence | | `whisper_is_vpn` | `is_vpn` | Threat Intelligence | | `whisper_is_whitelist` | `is_whitelist` | Threat Intelligence | The sourcetype `whisper:enrichment` is tagged with CIM tags: `network`, `resolution`, `dns`. Also set automatically: - `vendor` = `Whisper Security` - `vendor_product` = `Whisper Knowledge Graph` ## Caching All enrichment results are cached in the `whisper_enrichment_cache` KV Store collection. | Setting | Default | Description | |---------|---------|-------------| | Cache TTL | 3600 seconds (1 hour) | How long cached results are valid | | Cache collection | `whisper_enrichment_cache` | KV Store collection name | The cache is keyed by `indicator` + `indicator_type`. The `Whisper - Evict Expired Cache Entries` saved search runs hourly (when enabled) to clean up expired entries. To manually flush the cache: ```spl | whisperflush collection=cache ``` ## Pre-computed watchlist enrichment If you have indicators that need instant enrichment (say, for alerts), you can pre-compute results on a schedule: 1. Create a CSV or KV Store collection with indicators to watch 2. Configure the **Whisper Watchlist Enrichment** modular input 3. Set the enrichment interval (minimum 300 seconds) 4. Pre-computed results are stored in `whisper_precomputed_enrichment` The `whisperlookup` command checks the pre-computed collection before making API calls. ## Performance ### Throughput | Scenario | Throughput | Notes | |----------|-----------|-------| | Cache hit | 5,000+ events/sec | KV Store lookup only, no API call | | Cache miss (IP) | 10-30 events/sec | Single API call per unique IP | | Cache miss (domain) | 8-25 events/sec | Two-stage query (resolve + infrastructure) | | Mixed (80% cache hit) | 500-2,000 events/sec | Typical production workload | ### Optimization tips - **Specify indicator type:** Use `type=ip` or `type=domain` instead of `type=auto` to skip type detection - **Disable unused enrichment:** Set `include_threat_intel=false`, `include_cname=false`, `include_nameserver=false` to skip API calls you do not need - **Filter before enriching:** Apply `where` or `search` filters before `whisperlookup` to reduce the number of indicators - **Use pre-built macros:** Common investigation patterns are optimized in the 8 macros - **Monitor cache hit rates:** Caching reduces API calls by 5-10x for repeated indicators. Check `| inputlookup whisper_enrichment_cache | stats count` to monitor cache size - **Pre-compute for alerting:** Use the watchlist input for indicators that need instant lookup without API latency --- ### Splunk Dashboards Reference Markdown: https://www.whisper.security/docs/integrations/splunk/dashboards.md HTML: https://www.whisper.security/docs/integrations/splunk/dashboards ## Overview The TA ships with 5 dashboards focused on enrichment, investigation, and compliance. All dashboards use Splunk Dashboard Studio (JSON v2), which supports dark mode and Splunk Cloud. All data-facing dashboards reference the `whisper_index` macro instead of a hardcoded index name. By default, this macro resolves to `index=whisper`. To use a different index, override the macro in **Settings > Advanced Search > Search Macros** or create a `local/macros.conf` override. ## Navigation After installing the TA, navigate to **Apps > Whisper Security TA**. The navigation is organized around the three customer workflows: - **Investigation** - **Lookup / Investigation** (default) -- Ad hoc domain/IP investigation with multiple read-only graph pivots - **Attack Surface** - **Attack Surface Change Timeline** -- DNS infrastructure change timeline with risk scoring (driven by the owned-domain modular input) - **Compliance and Posture** - **Compliance Summary** -- Executive compliance overview - **SPF Compliance** -- SPF authentication analysis - **Mail Configuration** -- MX record monitoring - **Search** -- Ad hoc SPL search view - **Inputs** -- Manage modular inputs (owned-domain monitoring, etc.) - **Configuration** -- API key and account settings ## Lookup / Investigation dashboard Ad hoc domain and IP investigation using the Whisper Knowledge Graph. Enter an indicator and click **Submit** to run a set of read-only graph pivots in parallel. The dashboard does not write data, does not use `collect`, and does not generate risk events or alerts. Panels (semantics corrected per #480): | Panel | What it shows | |-------|---------------| | whisperlookup enrichment | Full enrichment table -- IP, ASN, country, risk, threat feeds, CNAME chain | | Threat feed / explain | `CALL explain()` result: score, level, factors, sources | | Shared nameservers | **Peer hostnames** that share at least one nameserver with the indicator, ordered by `shared_ns_count` (most-shared first). Useful for finding infrastructure siblings. | | Co-hosted domains / shared IP | Domains sharing a resolved IP with the indicator | | WHOIS pivots | **Related domains** sharing the indicator's `HAS_REGISTRAR`, `REGISTERED_BY` (organization), `HAS_EMAIL`, or `HAS_PHONE` attribute. Each row is tagged with the `pivot` type (registrar / organization / email / phone), the `shared_value`, and the `peer_domain`. | | WHOIS / BGP history | `CALL whisper.history()` output -- previous registrar, ownership changes | | CNAME chain | ALIAS_OF hops (up to 5) | | SPF include chain | SPF_INCLUDE hops (up to 5) | | MX / mail infrastructure | MAIL_FOR mail servers and their IPs | | Subdomains | CHILD_OF subdomains of the indicator | | ASN / BGP / prefix context | Full BGP path using the verified graph traversal `HOSTNAME → IPV4 → ANNOUNCED_PREFIX → ASN → ASN_NAME`. Exposes the announced `prefix` (CIDR), `asn`, and `asn_name` per resolved IP. | | Web links | True inbound and outbound `LINKS_TO` edges (`UNION ALL` of directed patterns) with linked host, `direction` (`inbound` / `outbound`), and `feed_count`. | **Input:** Indicator (domain or IP). Panels only run after **Submit** is clicked, so the dashboard makes no API calls on load. **Indicator validation:** Each pivot panel passes `$indicator_input$` directly to `whisperquery` as the `indicator` Cypher parameter and enables the server-side allowlist validator via `validate_indicator="indicator"`. The command lowercases the value and gates it on the regex allowlist `^[A-Za-z0-9._:\-]+$`; empty or malicious input short-circuits with zero events before any Cypher executes. The enrichment panel uses the equivalent inline SPL guard (`| makeresults | eval indicator=lower("$indicator_input$") | where match(indicator, "^[A-Za-z0-9._:\-]+$") | whisperlookup field=indicator`) because `whisperlookup` is a streaming command and can consume a pipeline. Cypher queries are parameterized (`$indicator` placeholder) rather than string-interpolated. **Example SPL matching what the dashboard runs:** ```spl | makeresults | eval indicator="example.com" | whisperlookup field=indicator ``` ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $indicator})-[:RESOLVES_TO]->(ip:IPV4) RETURN h.name, ip.name LIMIT 10" params="indicator=example.com" ``` ## SPF Compliance dashboard Shows SPF (Sender Policy Framework) configuration for your monitored domains. Panels: | Panel | Description | |-------|-------------| | Domains with SPF | Percentage of monitored domains with SPF records | | Exceeds 10-Lookup Limit | Domains violating RFC 7208 | | Total Authorized IPs | Sum of authorized sending IPs across all domains | | SPF Compliance Status | Per-domain status table with drill-down | | SPF Include Chain | Include chain for selected domain | | Authorized Sending IPs | Authorized IPs for selected domain | **Filters:** Time range, domain filter **Drill-down:** Click a domain row to see its SPF include chain and authorized sending IPs. ## Mail Configuration dashboard Shows MX (Mail Exchange) record configuration and changes. Panels: | Panel | Description | |-------|-------------| | Domains with MX Records | Count of domains with mail servers | | Total Mail Servers | Distinct MX record count | | Recent MX Changes | Change count (0 = stable, 1+ = review needed) | | Mail Server Configuration | Per-domain MX record table with drill-down | | Mail Server Changes | Change history table | | Mail Server Details | Detail for selected domain | **Filters:** Time range, domain filter ## Attack Surface Change Timeline dashboard Shows the timeline of DNS infrastructure changes detected across your monitored domains, ranked by risk score and broken down by record type. Panels: | Panel | Description | |-------|-------------| | Total Changes | Count of all change events in the selected time range | | High-Risk Changes | Changes with `risk_score >= 40` | | Domains Affected | Distinct domain count among change events | | Change Volume Over Time | Daily change count split by `change_type` (added / removed) | | Risk Score Trend | Average and maximum risk score per day | | Change Type Breakdown | Top change types by count | | High-Risk Changes Table | Recent high-risk changes with MITRE ATT&CK technique annotations | | All Changes Table | Full change history with old/new values | **Filters:** Time range **Data dependency:** This dashboard reads `sourcetype=whisper:attack_surface_change` events. The Attack Surface Baseline modular input emits these events on its second and subsequent runs (the first run only writes the baseline; change detection starts from the next run). If the dashboard is empty, verify that the baseline input is enabled and has run at least twice. ## Compliance Summary dashboard Compliance overview across all monitored domains, built for management reporting. Panels: | Panel | Description | |-------|-------------| | Overall Compliance Score | SPF compliance rate | | Monitored Domains | Distinct domain count | | Infrastructure Changes | Total change events | | NIS2 Article 21 | DNS monitoring regulatory status | | DMARC Enforcement Readiness | SPF readiness for DMARC deployment | | Attack Surface Inventory | Domain, subdomain, IP, NS, MX counts | | Infrastructure Change Timeline | Change volume over time by record type | **Filters:** Time range (default: 7 days) --- ## Saved searches The add-on ships a small set of disabled utility searches and example enrichment pipeline templates. It does not ship prebuilt correlation searches. Customers who want detections clone an example template, point it at their own source index and indicator field, and enable it. See [Saved searches](https://www.whisper.security/docs/integrations/splunk/saved-searches.md) for the complete list and the customization parameters. --- ## Attack surface monitoring Tracks DNS infrastructure changes for your external-facing domains on a schedule. ### DNS baseline collection The Whisper DNS Baseline modular input collects: - A records (IP resolution) - Nameservers (authoritative NS) - Mail servers (MX records) - Subdomains - CNAME chains Events are written with `sourcetype=whisper:attack_surface`. ### Configuration 1. Navigate to **Apps > Whisper Security TA > Inputs** 2. Create a new **Whisper DNS Baseline** input 3. Enter the domain list (comma-separated or one per line) 4. Set the collection interval (default: 24 hours, minimum: 1 hour) 5. Select the destination index --- ### Splunk Lookups & KV Store Reference Markdown: https://www.whisper.security/docs/integrations/splunk/lookups.md HTML: https://www.whisper.security/docs/integrations/splunk/lookups The add-on ships a small set of CSV lookup files plus several KV Store collections. Customize the CSVs to tune enrichment and to support any detections you build against the Whisper data. Sections that reference detection logic describe patterns you can build by cloning the example enrichment templates in `savedsearches.conf`; the add-on does not ship prebuilt correlation searches. ## CSV Lookups ### whisper_high_risk_asns.csv Autonomous Systems known for hosting malicious infrastructure (bulletproof hosting). Communication with IPs on these ASNs triggers the **Bulletproof ASN Communication Detection** correlation search. | Column | Description | |--------|-------------| | `asn` | ASN identifier (e.g., `AS44477`) | | `description` | Provider name | | `category` | Classification (e.g., `bulletproof_hosting`) | **Default entries:** SERVERD (AS200052), Stark Industries (AS44477), M247 (AS9009), Aeza Group (AS210644), Delis (AS48693), Reba Communications (AS213371), Flyservers (AS14061), PQ Hosting (AS216319) **To customize:** 1. Navigate to **Settings > Lookups > Lookup table files** 2. Find `whisper_high_risk_asns` and click **Edit** 3. Add or remove ASN entries 4. Changes take effect immediately for correlation searches Alternatively, create `local/whisper_high_risk_asns.csv` in the app directory to override without modifying the default. ### whisper_dns_providers.csv Major DNS providers excluded from the **Shared Nameserver with Threat Infrastructure** correlation search. Shared nameservers on these providers are expected and not suspicious. | Column | Description | |--------|-------------| | `nameserver_pattern` | Wildcard match pattern (e.g., `*.ns.cloudflare.com`) | | `provider` | Provider name | **Default providers:** Cloudflare, Google Cloud DNS, AWS Route 53, Azure DNS, Oracle Dyn, NS1, UltraDNS, DNSimple, Namecheap, GoDaddy, Hover, Gandi, OVH **Match type:** `WILDCARD` — patterns use `*` for prefix matching. ### whisper_cdn_asns.csv CDN and major SaaS provider ASNs excluded from the **Low Co-Hosting Density Anomaly** correlation search. IPs on CDN ASNs naturally host many domains, so low co-hosting is not anomalous. | Column | Description | |--------|-------------| | `asn` | ASN identifier | | `provider` | Provider name | **Default providers:** Cloudflare (AS13335), Amazon/AWS (AS16509, AS14618), Google (AS15169), Microsoft Azure (AS8075), Akamai (AS20940), Fastly (AS54113), Meta (AS32934), YouTube (AS36040), Twitch (AS46489), DigitalOcean (AS14061), Linode (AS63949) ### whisper_org_asns.csv Organizational ASN list for the **BGP Prefix Conflict Detection** correlation search. Populate this with your organization's ASNs to monitor for BGP prefix conflicts (potential hijacking). | Column | Description | |--------|-------------| | `asn` | ASN identifier (e.g., `AS15169`) | | `description` | Organization or description | **Default:** Empty (header only). You must populate this with your ASNs for the BGP conflict search to work. **To customize:** 1. Navigate to **Settings > Lookups > Lookup table files** 2. Find `whisper_org_asns` and click **Edit** 3. Add your organizational ASNs (one per row) 4. Enable the "Whisper - BGP Prefix Conflict Detection" correlation search **Example:** ```csv asn,description AS15169,Google AS36040,Google Cloud AS8075,Microsoft Azure ``` ### whisper_risk_factors.csv Configurable weights for the risk scoring algorithm. Each factor contributes points to the overall risk score (0-100 scale). | Column | Description | |--------|-------------| | `factor` | Factor identifier | | `points` | Point contribution (negative = risk reduction) | | `description` | Human-readable description | **Default factors:** | Factor | Points | Trigger | |--------|--------|---------| | `bulletproof_asn` | 60 | IP on a bulletproof hosting ASN (static fallback) | | `asn_high_threat` | 60 | ASN has HIGH or CRITICAL threat level from API | | `asn_medium_threat` | 30 | ASN has MEDIUM or SUSPICIOUS threat level from API | | `threat_category_c2` | 70 | Indicator is C2 infrastructure (`is_c2=true`) | | `threat_category_malware` | 60 | Indicator distributes malware (`is_malware=true`) | | `threat_category_phishing` | 50 | Indicator is phishing infrastructure (`is_phishing=true`) | | `threat_category_bruteforce` | 40 | Indicator is a brute-force source (`is_bruteforce=true`) | | `threat_category_tor` | 30 | Indicator is a Tor exit node (`is_tor=true`) | | `threat_category_scanner` | 25 | Indicator is a network scanner (`is_scanner=true`) | | `threat_category_anonymizer` | 20 | Indicator is an anonymization service (`is_anonymizer=true`) | | `threat_category_blacklist` | 20 | Indicator is on a general blacklist (`is_blacklist=true`) | | `threat_whitelist` | -30 | Indicator is on a reputation whitelist — reduces score | | `high_cohosting` | 30 | IP hosts 500+ domains | | `low_cohosting` | 15 | IP hosts fewer than 5 domains (dedicated infra) | | `suspicious_spf` | 20 | SPF record issues | | `threat_feed_low` | 40 | Listed in 1 threat feed | | `threat_feed_medium` | 60 | Listed in 2-3 threat feeds | | `threat_feed_high` | 80 | Listed in 4+ threat feeds | | `known_cdn` | -20 | IP on a known CDN ASN (reduces score) | | `shared_ns_threat` | 50 | Shares nameserver with threat infra | > **Overriding defaults:** > Add a row with the same `factor` name to `whisper_risk_factors.csv` to override any default weight. The CSV value takes precedence over the built-in default for that factor. **Risk levels:** | Range | Level | |-------|-------| | 0-19 | LOW | | 20-39 | MEDIUM | | 40-59 | HIGH | | 60-100 | CRITICAL | ## KV Store Collections Five KV Store collections are created automatically. They are managed by modular inputs and search commands — direct editing is not recommended. | Collection | Purpose | Populated By | |------------|---------|-------------| | `whisper_enrichment_cache` | TTL cache for live enrichment | `whisperlookup` command | | `whisper_precomputed_enrichment` | Pre-computed watchlist data | Watchlist input | | `whisper_ip_intel` | ES IP threat intelligence | Threat intel input | | `whisper_domain_intel` | ES domain threat intelligence | Threat intel input | | `whisper_dns_baseline` | DNS infrastructure snapshots | Baseline input | **Cache management:** ```spl | whisperflush collection=cache # Clear enrichment cache | whisperflush collection=precomputed # Clear precomputed data | whisperflush collection=all # Clear everything ``` The **Whisper - Evict Expired Cache Entries** saved search (disabled by default) runs hourly to remove expired cache entries. Enable it under **Settings > Searches, reports, and alerts**. --- ### Modular Inputs Markdown: https://www.whisper.security/docs/integrations/splunk/modular-inputs.md HTML: https://www.whisper.security/docs/integrations/splunk/modular-inputs The Whisper Security Add-on includes three modular inputs for automated data collection. All are configured via the **Inputs** page in the add-on UI and disabled by default. ## ES Threat Intelligence Feed Populates the Splunk ES threat intelligence framework with scored indicators from the Whisper `explain()` API. | Setting | Default | Range | Description | |---------|---------|-------|-------------| | Interval | 21600s (6 hr) | 300-86400 | Collection frequency | | Max Indicators | 10000 | 1-100000 | Indicators per run | | Include Infrastructure | off | — | Add ASN/country/prefix context | | Account | required | — | Whisper API account | | Index | `whisper` | — | Destination index | **Output:** `sourcetype=whisper:threat_intel` ### Where do threat indicators come from? The input populates two KV Store collections: | Collection | Key Field | Description | |------------|-----------|-------------| | `whisper_ip_intel` | `ip` | IP indicators with threat scores, ASN, country | | `whisper_domain_intel` | `domain` | Domain indicators with threat scores | These integrate with ES via the threat intelligence framework. Correlation searches reference them automatically. **Automatic seeding:** On first run, when both KV Store collections are empty, the input automatically queries the Whisper Knowledge Graph for IPV4 and HOSTNAME nodes with `threatScore > 0`. This bootstraps the collections so threat intelligence flows immediately without manual setup. **After the first run**, the input re-assesses all existing indicators in the collections on each interval. To add new indicators, insert them into the appropriate collection: ```spl | makeresults | eval ip="203.0.113.50", description="Suspicious IP from investigation" | outputlookup whisper_ip_intel append=true ``` ```spl | makeresults | eval domain="malicious-example.com", description="Phishing domain" | outputlookup whisper_domain_intel append=true ``` **Verification:** ```spl | inputlookup whisper_ip_intel | head 10 | inputlookup whisper_domain_intel | head 10 ``` ## Attack Surface Baseline Collects DNS infrastructure snapshots for monitored domains. Used by the DNS Infrastructure Change Detection correlation search. | Setting | Default | Range | Description | |---------|---------|-------|-------------| | Interval | 86400s (24 hr) | 3600-604800 | Collection frequency | | Domains | required | — | Comma-separated domain list | | Account | required | — | Whisper API account | | Index | `whisper` | — | Destination index | ### How to specify domains Enter the domains you want to monitor as a **comma-separated list** in the **Domains** field when configuring the input. For example: ``` example.com, corp.example.com, subsidiary.com ``` The input discovers the full DNS infrastructure for each domain (A records, nameservers, mail servers, CNAME chains, and subdomains). You do not need to list subdomains individually — they are discovered automatically. **Records collected per domain:** | Record Type | Cypher Query | Description | |-------------|-------------|-------------| | A | `RESOLVES_TO → IPV4` | DNS A records | | NS | `NAMESERVER_FOR → HOSTNAME` | Nameservers | | MX | `MAIL_FOR → HOSTNAME` | Mail servers | | CNAME | `ALIAS_OF → HOSTNAME` (up to 5 hops) | CNAME chains | | SUBDOMAIN | `CHILD_OF → HOSTNAME` (up to 1000) | Subdomains | **Outputs:** | Sourcetype | When emitted | Purpose | |-----------|--------------|---------| | `whisper:attack_surface` | Every run | Per-record DNS baseline (one event per A/NS/MX/CNAME/SUBDOMAIN record) | | `whisper:spf_compliance` | Every run | One event per domain with SPF record analysis | | `whisper:attack_surface_change` | Second run onward | Diff between the previous run's snapshot and the current run; one event per added or removed record | | `index=risk` (sourcetype `stash`) | When NS, MX, or wildcard records change | High-priority risk events with MITRE ATT&CK technique annotations for ES Risk-Based Alerting | The input keeps a per-domain snapshot in the modular input checkpoint after each run. The next run compares the new baseline against the saved snapshot and emits change events. The first run after install therefore produces only baseline events; change detection begins on the second run. Each record is also written to the `whisper_dns_baseline` KV Store collection. That collection seeds the Watchlist Enrichment input (see below). **Verification:** ```spl `whisper_index` sourcetype="whisper:attack_surface" | stats count by domain, record_type ``` ## Watchlist Enrichment Pre-computes enrichment for a custom list of indicators, storing results in KV Store for instant lookup without live API calls. | Setting | Default | Range | Description | |---------|---------|-------|-------------| | Interval | 14400s (4 hr) | 300-86400 | Enrichment frequency | | Max Indicators | 10000 | 1-100000 | Indicators per run | | Account | required | — | Whisper API account | | Index | `whisper` | — | Destination index | **Output:** `sourcetype=whisper:watchlist` Results are stored in the `whisper_precomputed_enrichment` KV Store collection. The `whisperlookup` command checks this collection before making live API calls. ### How to populate the watchlist The input enriches all indicators in the `whisper_watchlist` KV Store collection. Each record has three fields: | Field | Required | Description | |-------|----------|-------------| | `indicator` | yes | Domain name or IP address | | `indicator_type` | no | `"domain"` or `"ip"` (auto-detected if omitted) | | `description` | no | Free-text note (e.g., why this indicator is watched) | **Add indicators via SPL:** ```spl | makeresults | eval indicator="example.com", indicator_type="domain", description="Primary domain" | append [| makeresults | eval indicator="203.0.113.50", indicator_type="ip", description="Critical server"] | outputlookup whisper_watchlist append=true ``` **Bulk-load from existing Splunk data:** ```spl index=firewall action=blocked | stats count by dest_ip | where count > 100 | rename dest_ip AS indicator | eval indicator_type="ip", description="Frequently blocked IP" | outputlookup whisper_watchlist append=true ``` **View current watchlist:** ```spl | inputlookup whisper_watchlist ``` **Automatic seeding:** When the watchlist collection is empty and an Attack Surface Baseline input has already run, the watchlist input automatically seeds itself with domains from the `whisper_dns_baseline` collection. This means your organization's known infrastructure domains are enriched by default without manual setup. You can add or remove indicators at any time after seeding. **Verification:** ```spl | inputlookup whisper_precomputed_enrichment | head 10 ``` ## Input scheduling guidelines | Input | Recommended Interval | Notes | |-------|---------------------|-------| | Threat Intel | 6 hours | Moderate — processes many indicators | | Baseline | 24 hours | Infrequent — DNS changes slowly | | Watchlist | 4 hours | Moderate — depends on watchlist size | All inputs respect the API rate limit. Intervals shorter than the minimum are rejected during configuration. --- ### Saved Searches Markdown: https://www.whisper.security/docs/integrations/splunk/saved-searches.md HTML: https://www.whisper.security/docs/integrations/splunk/saved-searches The add-on ships a small set of saved searches. All of them are disabled by default. ## Three workflows The saved searches support three workflows: 1. **Enrichment pipelines** -- Example templates that take events from a source index, run `whisperlookup` on an indicator field, and write the enriched events to a destination index the customer chooses. 2. **Indicator investigation** -- Not a scheduled search; this runs on demand through the [Lookup / Investigation dashboard](https://www.whisper.security/docs/integrations/splunk/dashboards.md). 3. **Owned-domain / attack-surface monitoring** -- Not a saved search; driven by the owned-domain modular input and visualised on the Attack Surface Change Timeline dashboard. ## What ships | Stanza | Purpose | |--------|---------| | `Whisper - Evict Expired Cache Entries` | Remove expired entries from the `whisper_enrichment_cache` KV Store. Enable if you use enrichment caching. | | `Whisper - Populate IP Threat Intel KV Store` | Optional ES Threat Intel populator for IPs. | | `Whisper - Populate Domain Threat Intel KV Store` | Optional ES Threat Intel populator for domains. | | `Whisper - Populate Precomputed Enrichment KV Store` | Optional -- pre-warm the enrichment cache for frequently used indicators. | | `Example - Whisper - Enrich DNS Domains` | Template: enrich DNS query domains and write to a destination index. | | `Example - Whisper - Enrich Destination IPs` | Template: enrich destination IPs from network traffic. | | `Example - Whisper - Enrich Proxy Hostnames` | Template: enrich proxy/web hostnames. | | `Example - Whisper - Custom Graph Query Enrichment` | Template: run a custom Cypher query for enrichment. | ## Customising an enrichment template Each template is disabled and uses placeholder parameters. To adopt one: 1. Copy the stanza into `local/savedsearches.conf` and rename it (e.g. `My Company - Enrich DNS Domains`). 2. Replace the placeholders: - `` -- the index containing your logs - `` -- the sourcetype filter to apply - `` / rename clause -- the field holding the IOC - `` -- the index where enriched events should land - `` -- a sourcetype to tag the enriched events 3. Adjust `dispatch.earliest_time` and `cron_schedule` to match your dedup window and schedule. 4. Set `disabled = 0` and `enableSched = 1`. Example template body: ```spl index= earliest=-15m =* | rename as indicator | dedup indicator | whisperlookup field=indicator type= | collect index= sourcetype="" ``` ## Building your own detections The add-on does not ship prebuilt correlation searches. To build a detection of your own, either: - Clone the relevant example enrichment template and point it at the source index you want to monitor, or - Write a detection in SPL using `whisperquery` against the Graph and the enrichment fields produced by `whisperlookup`. --- ### Agent Skills Markdown: https://www.whisper.security/docs/mcp/skills.md HTML: https://www.whisper.security/docs/mcp/skills Agent Skills are pre-built investigation workflows that run on top of the [Whisper MCP connector](https://www.whisper.security/docs/mcp/setup.md). They're open source and MIT-licensed at [github.com/whisper-sec/whisper-skills](https://github.com/whisper-sec/whisper-skills). The connector gives an AI assistant the *tools* — `query`, `explain_indicator`, `domain_variants`, and the rest. Skills give it the *playbooks*: which tool to reach for, in what order, with the real graph schema and validated query patterns already baked in. Instead of working a multi-step investigation out from scratch, the assistant loads a skill and runs a known-good workflow. ## The three skills | Skill | What it does | |-------|--------------| | `whisper-investigate` | Threat triage and indicator enrichment — chains tools to answer "is this domain or IP malicious?", enrich IOCs, and pivot through infrastructure. | | `whisper-cypher` | Cypher query authoring — bundles the graph schema, validated query patterns, and the query-safety rules so the assistant writes valid Cypher on the first try. | | `whisper-brand-protection` | Typosquatting and brand-protection sweeps — lookalike domain detection, registrant pivoting, and takedown-report generation. | Each skill loads automatically when your question matches it. Ask *"is 185.220.101.1 malicious?"* and `whisper-investigate` activates; ask *"find typosquats of paypal.com"* and `whisper-brand-protection` activates. ## Prerequisite: connect the MCP server Skills are workflows *for* the connector — they don't do anything on their own. Connect `https://mcp.whisper.security` first; see the [Setup guide](https://www.whisper.security/docs/mcp/setup.md). The skills build on six tools the connector exposes: `query`, `explain_indicator`, `whisper_history`, `domain_variants`, `list_labels`, and `describe_label`. ## Install Clone or download the repo first: ```bash git clone https://github.com/whisper-sec/whisper-skills.git ``` ### Claude.ai (Web) Zip a skill folder and upload it under **Settings → Capabilities → Skills**. Repeat for each skill you want available. ### Claude Code Copy the skill folders into your skills directory: ```bash ## All projects cp -r whisper-skills/whisper-* ~/.claude/skills/ ## This project only cp -r whisper-skills/whisper-* .claude/skills/ ``` ### API Include the skill folders in Messages API requests via the `container.skills` parameter, alongside the Code Execution tool. ## How it fits together ``` You ask a question ↓ A skill loads the workflow (whisper-investigate / -cypher / -brand-protection) ↓ The MCP connector runs the tools (query, explain_indicator, domain_variants, …) ↓ WhisperGraph answers (7.4B nodes, 39B edges, 5.6M threat-intel edges) ``` The connector also ships eight built-in [prompts](https://www.whisper.security/docs/mcp/reference.md) for common investigation patterns. Prompts are single-shot — you trigger one explicitly. Skills are richer, auto-loading workflows with the schema and patterns built in. Reach for a prompt when you want a quick one-off; install a skill for repeatable investigations. ## Next steps - [MCP Reference](https://www.whisper.security/docs/mcp/reference.md) — the tools, resources, and prompts the skills are built on. - [Cypher Cookbook](https://www.whisper.security/docs/cypher-query-cookbook.md) — the query patterns behind `whisper-cypher`, organised by analyst persona. - Contributions are welcome — the repo is MIT-licensed. Open an issue or PR at [github.com/whisper-sec/whisper-skills](https://github.com/whisper-sec/whisper-skills). --- ### Splunk Use Cases for Infrastructure Intel Markdown: https://www.whisper.security/docs/integrations/splunk/use-cases.md HTML: https://www.whisper.security/docs/integrations/splunk/use-cases The add-on does not ship a prebuilt correlation-search pack or an analytic story. To build detection logic of your own, clone one of the disabled example enrichment templates in `savedsearches.conf` and adapt it to your data model. ## Overview This page organizes the add-on's capabilities by security workflow. Each use case shows the problem, the Splunk commands that solve it, and the expected output. --- ## Threat intelligence enrichment ### Enrich firewall logs with infrastructure context **Problem:** Your firewall logs contain destination IPs, but you need ASN, country, and threat intelligence to prioritize alerts. **Solution:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip type=ip | where whisper_threat_score > 0 | table _time dest_ip whisper_asn_name whisper_country whisper_threat_level whisper_threat_score | sort -whisper_threat_score ``` ### Identify Tor exit nodes in your traffic **Problem:** Detect connections to or from known Tor exit nodes. **Solution:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip type=ip | where whisper_is_tor="true" | table _time src_ip dest_ip whisper_asn_name whisper_country ``` ### Find connections to bulletproof hosting **Problem:** Identify traffic to infrastructure hosted on ASNs known for hosting malicious content. **Solution:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip type=ip | lookup whisper_high_risk_asns_lookup asn AS whisper_asn OUTPUT asn_category | where isnotnull(asn_category) | table _time dest_ip whisper_asn whisper_asn_name asn_category ``` ### Cross-reference with multiple threat feeds **Problem:** Check if indicators appear in multiple threat intelligence feeds for higher confidence. **Solution:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip type=ip include_threat_intel=true include_feeds=true | where whisper_threat_sources_count > 2 | table dest_ip whisper_threat_level whisper_threat_sources_count whisper_feed_names ``` --- ## External Attack Surface Management (EASM) ### Monitor DNS infrastructure changes **Problem:** Detect unauthorized changes to your external DNS infrastructure (new subdomains, IP changes, NS migrations). **Setup:** Configure the Whisper DNS Baseline modular input with your domain list. The input runs on a schedule and collects DNS infrastructure snapshots. **Query baseline data:** ```spl sourcetype=whisper:attack_surface | stats count by domain record_type | sort -count ``` **View latest baseline for a domain:** ```spl sourcetype=whisper:attack_surface domain="example.com" | stats latest(record_value) AS current_value by record_type ``` ### Detect subdomain takeover risk **Problem:** Find dangling CNAME records that point to decommissioned services, creating subdomain takeover risk. **Solution:** ```spl | `whisper_cname_chain("cdn.yourdomain.com")` | table cname_chain cname_target depth ``` > **API plan requirement:** > The `whisper_cname_chain` macro requires a Professional API plan (5-hop traversal depth). ### Map your external infrastructure **Problem:** Get a complete inventory of your external-facing DNS, IP, and ASN infrastructure. **Solution:** ```spl sourcetype=whisper:attack_surface | stats dc(record_value) AS unique_records values(record_value) AS records by domain record_type | sort domain record_type ``` --- ## Incident investigation ### Full infrastructure investigation **Problem:** A suspicious domain or IP appears in your logs. You need to understand its complete infrastructure context. **Solution:** ```spl | `whisper_full_investigation("suspicious-domain.com")` ``` This macro returns: - IP addresses the domain resolves to - BGP prefix - ASN and country information - Co-hosted domain count ### Pivot on shared infrastructure **Problem:** You found a malicious domain. You need to find other domains sharing the same infrastructure. **Solution:** ```spl | `whisper_shared_nameservers("malicious-domain.com")` ``` ```spl | `whisper_cohosted_domains("malicious-domain.com")` ``` ### Investigate an ASN **Problem:** You identified a suspicious ASN. You need to see all prefixes and hostnames behind it. **Solution:** ```spl | `whisper_asn_infrastructure("AS12345")` ``` ### Trace BGP routing **Problem:** Verify that an IP's BGP routing matches its RIR registration to detect potential route hijacking. **Solution:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip type=ip | where whisper_bgp_hijack_detected="true" | table dest_ip whisper_bgp_announcing_asn whisper_bgp_registered_asn whisper_bgp_announced_prefix ``` ### WHOIS-based attribution **Problem:** Correlate domains by registration data to identify related threat infrastructure. **Solution:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:HAS_EMAIL]->(e:EMAIL)<-[:HAS_EMAIL]-(other:HOSTNAME) RETURN other.name AS related_domain, e.name AS shared_email LIMIT 25" params="domain=suspicious-domain.com" ``` --- ## Compliance monitoring ### SPF compliance audit **Problem:** Check which of your domains have valid SPF records and comply with the RFC 7208 10-lookup limit. **Solution:** ```spl | `whisper_spf_chain("yourdomain.com")` ``` Or view the SPF Compliance dashboard for all monitored domains. ### Mail server configuration audit **Problem:** Monitor MX record changes that could indicate email infrastructure compromise. **Solution:** The Mail Configuration dashboard tracks: - MX record inventory across all monitored domains - Recent MX changes - Per-domain mail server configuration history --- ## Risk-based alerting ### Enable correlation searches **Problem:** You want automated alerting when Whisper data indicates infrastructure risk. **Setup:** 1. Navigate to **Settings > Searches, Reports, and Alerts** 2. Enable the correlation searches relevant to your environment: | Search | Risk signal | |--------|------------| | Bulletproof ASN Communication | Traffic to known bulletproof hosting | | Shared Nameserver with Threat Infrastructure | DNS infrastructure overlap with threats | | DNS Infrastructure Change Detection | Unexpected DNS changes | | Low Co-Hosting Density Anomaly | Dedicated threat infrastructure | | BGP Prefix Conflict Detection | Route hijacking indicators | | Impossible Travel Detection | Geographically inconsistent resolution | All enabled searches generate risk events with MITRE ATT&CK annotations for ES Risk-Based Alerting. ### Custom risk-based workflows **Problem:** You want to build custom alerts based on Whisper enrichment data. **Solution:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip type=ip | where whisper_threat_score >= 50 AND whisper_is_threat="true" | eval risk_message="Connection to high-threat IP " . dest_ip . " (ASN: " . whisper_asn_name . ", Score: " . whisper_threat_score . ")" | collect index=risk risk_score=whisper_threat_score risk_object=dest_ip risk_object_type=system ``` --- --- ## Cypher equivalents in the Cookbook Every Splunk use case below has a Cypher counterpart in the Cookbook. Use Cypher when you want to pivot through the graph; use SPL when you want enrichment inline with your Splunk events. - Firewall enrichment, Tor detection → [SOC recipes](https://www.whisper.security/docs/recipes/soc.md) - Bulletproof hosting, ASN reputation → [Threat-intel recipes](https://www.whisper.security/docs/recipes/threat-intel.md) - BGP hijack detection → [BGP recipes](https://www.whisper.security/docs/recipes/bgp-routing.md) - SPF/DMARC posture, dangling DNS → [DNS/email recipes](https://www.whisper.security/docs/recipes/dns-email.md) - External attack-surface monitoring → [Pentest recon recipes](https://www.whisper.security/docs/recipes/pentest-recon.md) - Insurance / third-party risk scoring → [Insurance & TPRM recipes](https://www.whisper.security/docs/recipes/insurance-risk.md) --- ### Investigation Macros Markdown: https://www.whisper.security/docs/integrations/splunk/macros.md HTML: https://www.whisper.security/docs/integrations/splunk/macros SPL macros for common investigations against the Whisper Knowledge Graph. All of them wrap `whisperquery` with a parameterized Cypher query. ## Usage Call a macro with backtick syntax: ```spl | `whisper_shared_nameservers("example.com")` ``` ## Available macros ### whisper_shared_nameservers(domain) Find all domains sharing nameservers with the given domain. Good for spotting related infrastructure or attacker-controlled domain clusters. ```spl | `whisper_shared_nameservers("phishing-target.com")` ``` **Output fields:** `nameserver`, `related_domain` During IR, use this to find other domains on the same nameserver as a phishing domain. Shared nameservers often mean common ownership or compromised hosting. --- ### whisper_asn_infrastructure(asn) List all prefixes routed by an ASN. ```spl | `whisper_asn_infrastructure("AS13335")` ``` **Output fields:** `asn`, `prefix` Handy for scoping a bulletproof hosting provider or mapping out a threat actor's ASN. --- ### whisper_cname_chain(domain) Resolve the full CNAME alias chain for a domain, up to 5 hops deep. ```spl | `whisper_cname_chain("www.example.com")` ``` **Output fields:** `cname_chain`, `cname_target`, `depth` Good for detecting dangling CNAMEs (subdomain takeover risk) and understanding delegation patterns. --- ### whisper_spf_chain(domain) Trace the SPF include mechanism chain for a domain. Checks for RFC 7208 compliance (maximum 10 DNS lookups). ```spl | `whisper_spf_chain("example.com")` ``` **Output fields:** `spf_chain`, `depth` Use this to audit email auth compliance and catch SPF configs that are about to hit the 10-lookup limit. --- ### whisper_bgp_peers(asn) List BGP peers of an ASN with peer names and country information. ```spl | `whisper_bgp_peers("AS13335")` ``` **Output fields:** `peer_asn`, `peer_name`, `country` Helps you see the transit relationships of a network and spot unusual peering arrangements. --- ### whisper_cohosted_domains(domain) Find domains co-hosted on the same IP address as the given domain. ```spl | `whisper_cohosted_domains("suspicious-site.com")` ``` **Output fields:** `ip`, `cohosted_domain` Look for other potentially malicious domains on the same IP. Low co-hosting density (dedicated IPs) is a common sign of attacker-controlled infrastructure. --- ### whisper_full_investigation(indicator) Full infrastructure investigation: hostname to IP to ASN with geo context and co-hosting count. ```spl | `whisper_full_investigation("malware-c2.evil.com")` ``` **Output fields:** `hostname`, `ip`, `prefix`, `asn`, `asn_name`, `country`, `cohost_count` One-command infrastructure overview during triage. Combines domain resolution, ASN lookup, geolocation, and co-hosting analysis. --- ### whisper_explain(indicator) Get a threat assessment for an indicator via the Whisper explain API. ```spl | `whisper_explain("8.8.8.8")` ``` **Output fields:** Results from the `explain()` stored procedure, including threat score, level, explanation, and contributing factors. Quick threat check on a single indicator without running the full enrichment pipeline. --- ## Index macro | Macro | Default | Description | |-------|---------|-------------| | `whisper_index` | `index=whisper` | Default index for Whisper Security events. Override it in **Settings > Advanced Search > Search Macros** if you write Whisper events to a custom index. | --- ## Combining macros with SPL Macros return tabular results, so you can pipe them into any SPL command: **Filter shared nameserver results by country:** ```spl | `whisper_shared_nameservers("target.com")` | lookup whisper_domain_intel domain AS related_domain | where isnotnull(threat_key) ``` **Investigate suspicious ASNs from enriched events:** ```spl index=firewall sourcetype=pan:traffic | whisperlookup field=dest_ip | where whisper_cohost_count < 5 | dedup whisper_asn | map search="| `whisper_asn_infrastructure(\"$$whisper_asn$$\")`" ``` **Export CNAME chains to a lookup:** ```spl | `whisper_cname_chain("example.com")` | outputlookup whisper_cname_results.csv ``` --- ### Cypher Reference for Splunk Markdown: https://www.whisper.security/docs/integrations/splunk/cypher-reference.md HTML: https://www.whisper.security/docs/integrations/splunk/cypher-reference > **Note:** This is a quick reference for using Cypher within Splunk. For the complete Cypher language reference, see the [Cypher Language Reference](https://www.whisper.security/docs/cypher-query-guide.md). For API details, see the [Cypher API Reference](https://www.whisper.security/docs/cypher-api-reference.md). Reference for writing Cypher queries against the Whisper Knowledge Graph from Splunk using the `whisperquery` command or by understanding the queries behind `whisperlookup` and the pre-built macros. ## Basics Cypher is a declarative graph query language. The general shape of a query: ```cypher MATCH (pattern) WHERE condition RETURN fields LIMIT N ``` ### Node patterns ```cypher (n) // any node (n:HOSTNAME) // node with label HOSTNAME (n:HOSTNAME {name: "x.com"}) // node with label and property ``` ### Relationship patterns ```cypher (a)-[:RESOLVES_TO]->(b) // directed relationship (a)-[:ALIAS_OF*1..5]->(b) // variable-length path (1 to 5 hops) (a)<-[:NAMESERVER_FOR]-(b) // reverse direction ``` ### Property matching Properties are case-sensitive. Domain names are stored in lowercase: ```cypher // Correct MATCH (h:HOSTNAME {name: "example.com"}) // Wrong (will return no results) MATCH (h:HOSTNAME {name: "Example.com"}) ``` --- ## Graph schema ### Node labels | Label | Description | Example values | |-------|-------------|----------------| | HOSTNAME | Fully-qualified domain names, subdomains, mail server names | `www.google.com`, `ns1.cloudflare.com` | | IPV4 | IPv4 addresses | `1.1.1.1`, `142.250.64.100` | | IPV6 | IPv6 addresses | `2606:4700::6810:84e5` | | PREFIX | IP CIDR blocks | `142.250.64.0/24` | | ASN | Autonomous system numbers | `AS13335`, `AS15169` | | ASN_NAME | Human-readable AS organization names | `CLOUDFLARENET - Cloudflare, Inc.` | | TLD | Top-level domains | `com`, `net`, `org`, `io` | | TLD_OPERATOR | TLD registry operators | `VeriSign, Inc.` | | REGISTRAR | Domain registrars (IANA ID format) | `iana:292` (MarkMonitor) | | EMAIL | WHOIS contact email addresses | `domains@cloudflare.com` | | PHONE | WHOIS contact phone numbers (E.164) | `+14158675825` | | ORGANIZATION | Organizations from WHOIS records | `cloudflare hostmaster` | | CITY | GeoIP city with country code | `Mountain View, US` | | COUNTRY | ISO 3166-1 alpha-2 country codes | `US`, `DE`, `AU` | | RIR | Regional Internet Registries | `ARIN`, `RIPENCC`, `APNIC`, `LACNIC`, `AFRINIC` | | DNSSEC_ALGORITHM | DNSSEC signing algorithms | `ECDSAP256SHA256`, `RSASHA256` | | FEED_SOURCE | Threat intelligence feed sources (virtual) | `Spamhaus DROP`, `Feodo Tracker` | | CATEGORY | Threat feed categories (virtual) | `C2 Servers`, `Phishing` | **Virtual labels** (synthesized at query time, global `count()` returns 0): | Label | Description | |-------|-------------| | REGISTERED_PREFIX | RIR-allocated prefix; has `HAS_COUNTRY` and `REGISTERED_BY` edges | | ANNOUNCED_PREFIX | BGP-announced prefix; has `ROUTES` (to ASN) and `HAS_COUNTRY` edges | ### Edge types **DNS resolution** | Edge type | Source to Target | Description | |-----------|-----------------|-------------| | RESOLVES_TO | HOSTNAME to IPV4/IPV6 | DNS A/AAAA records | | CHILD_OF | HOSTNAME to HOSTNAME/TLD | Domain hierarchy (sub.example.com -> example.com -> com) | | ALIAS_OF | HOSTNAME to HOSTNAME | CNAME records | | NAMESERVER_FOR | HOSTNAME to HOSTNAME | NS delegation (nameserver serves the target domain) | | MAIL_FOR | HOSTNAME to HOSTNAME | MX records (mail server handles mail for the target domain) | | SIGNED_WITH | HOSTNAME to DNSSEC_ALGORITHM | DNSSEC signing algorithm | **BGP and routing** | Edge type | Source to Target | Description | |-----------|-----------------|-------------| | ANNOUNCED_BY | IPV4/IPV6 to ANNOUNCED_PREFIX | BGP announcement (virtual, resolved at query time) | | ROUTES | ASN to ANNOUNCED_PREFIX | ASN routes this prefix (virtual) | | BELONGS_TO | IPV4 to PREFIX/REGISTERED_PREFIX/ANNOUNCED_PREFIX | IP membership in a prefix block | | PEERS_WITH | ASN to ASN | BGP peering session (virtual) | | HAS_NAME | ASN to ASN_NAME | Network operator name (virtual) | | CONFLICTS_WITH | PREFIX to ASN | MOAS conflict — same prefix announced by more than one ASN (virtual) | | HAS_COUNTRY | ASN/PREFIX/CITY/IPV4/HOSTNAME/PHONE/ANNOUNCED_PREFIX/REGISTERED_PREFIX to COUNTRY | Country association | **WHOIS and registration** | Edge type | Source to Target | Description | |-----------|-----------------|-------------| | HAS_REGISTRAR | HOSTNAME to REGISTRAR | Current domain registrar | | PREV_REGISTRAR | HOSTNAME to REGISTRAR | Previous domain registrar | | REGISTERED_BY | HOSTNAME/ASN/PREFIX to ORGANIZATION | WHOIS / RIR registration | | HAS_EMAIL | HOSTNAME to EMAIL | WHOIS contact email | | HAS_PHONE | HOSTNAME to PHONE | WHOIS contact phone | **GeoIP** | Edge type | Source to Target | Description | |-----------|-----------------|-------------| | LOCATED_IN | IPV4/IPV6 to CITY | GeoIP city location. To reach COUNTRY, chain through `HAS_COUNTRY` — `LOCATED_IN` never targets COUNTRY | **Threat intelligence** | Edge type | Source to Target | Description | |-----------|-----------------|-------------| | LISTED_IN | IPV4/IPV6/HOSTNAME to FEED_SOURCE | IP or hostname appears in this threat feed (virtual) | | BELONGS_TO | FEED_SOURCE to CATEGORY | Feed classified under this category | **Web** | Edge type | Source to Target | Description | |-----------|-----------------|-------------| | LINKS_TO | HOSTNAME to HOSTNAME | Hyperlink between hostnames (from web crawl data) | **SPF** | Edge type | Source to Target | Description | |-----------|-----------------|-------------| | SPF_INCLUDE | HOSTNAME to HOSTNAME | SPF `include:` mechanism | | SPF_IP | HOSTNAME to IPV4/IPV6/PREFIX | SPF `ip4:` / `ip6:` mechanism | | SPF_A | HOSTNAME to HOSTNAME | SPF `a:` mechanism | | SPF_MX | HOSTNAME to HOSTNAME | SPF `mx:` mechanism | | SPF_REDIRECT | HOSTNAME to HOSTNAME | SPF `redirect=` modifier | | SPF_EXISTS | HOSTNAME to HOSTNAME | SPF `exists:` mechanism | **Other** | Edge type | Source to Target | Description | |-----------|-----------------|-------------| | OPERATES | TLD_OPERATOR to TLD | Registry operator manages this TLD (virtual) | ### Entity relationship diagram ![Diagram](https://whisper.cdn.prismic.io/whisper/afJjesBOoF08xcsu_splunk-cypher-reference-diagram-0.svg) Solid lines are physical edges stored on disk. Dashed lines are virtual edges computed at query time from live infrastructure and threat intelligence data. ### Traversal chains Common multi-hop paths through the graph. These are the patterns used by `whisperlookup` and the pre-built macros. | Chain | Path | |-------|------| | DNS resolution | `HOSTNAME -> RESOLVES_TO -> IPV4 -> ANNOUNCED_BY -> ANNOUNCED_PREFIX -> ROUTES -> ASN -> HAS_NAME -> ASN_NAME` | | GeoIP (from IP) | `IPV4 -> LOCATED_IN -> CITY -> HAS_COUNTRY -> COUNTRY` | | GeoIP (from domain) | `HOSTNAME -> RESOLVES_TO -> IPV4 -> LOCATED_IN -> CITY -> HAS_COUNTRY -> COUNTRY` | | BGP routing | `ASN -> ROUTES -> ANNOUNCED_PREFIX`, `ASN -> PEERS_WITH -> ASN` | | DNS hierarchy | `HOSTNAME -> CHILD_OF -> HOSTNAME(parent) -> CHILD_OF -> TLD` | | DNS security | `HOSTNAME <- NAMESERVER_FOR <- HOSTNAME`, `HOSTNAME -> SIGNED_WITH -> DNSSEC_ALGORITHM` | | WHOIS | `HOSTNAME -> HAS_REGISTRAR -> REGISTRAR`, `HOSTNAME -> HAS_EMAIL -> EMAIL`, `HOSTNAME -> REGISTERED_BY -> ORGANIZATION` | | Threat intel | `IPV4/HOSTNAME -> LISTED_IN -> FEED_SOURCE -> BELONGS_TO -> CATEGORY` | | SPF chain | `HOSTNAME -> SPF_INCLUDE -> HOSTNAME`, `HOSTNAME -> SPF_IP -> PREFIX` | --- ## Language reference For the complete language reference, see the [Cypher Language Reference](https://www.whisper.security/docs/cypher-query-guide#language-reference). ## Supported functions For the complete supported functions, see the [Cypher Language Reference](https://www.whisper.security/docs/cypher-query-guide#supported-functions). ## Procedures For the complete procedures, see the [Cypher Language Reference](https://www.whisper.security/docs/cypher-query-guide#procedures). ## Query cookbook All examples use the `whisperquery` command. Where a pre-built macro exists, it is noted. ### Incident investigation Trace an IP through DNS, network, and routing layers: **Identify the network owner:** ```spl | whisperquery query="MATCH (ip:IPV4 {name: $ip})-[: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 asn_name LIMIT 10" params="ip=142.250.64.100" ``` > **ANNOUNCED_BY vs BELONGS_TO:** > `ANNOUNCED_BY` uses live BGP routing data for current routing info. `BELONGS_TO` returns the registered RIR allocation block. They may give different results. For IPs where BGP data is unavailable, fall back to `BELONGS_TO`. **GeoIP lookup:** ```spl | whisperquery query="MATCH (ip:IPV4 {name: $ip})-[:LOCATED_IN]->(city:CITY)-[:HAS_COUNTRY]->(country:COUNTRY) RETURN ip.name AS ip, city.name AS city, country.name AS country LIMIT 1" params="ip=142.250.64.100" ``` **Count domains on an IP (co-hosting):** ```spl | whisperquery query="MATCH (ip:IPV4 {name: $ip})<-[:RESOLVES_TO]-(h:HOSTNAME) RETURN count(h) AS cohosted_domains LIMIT 1" params="ip=104.16.123.96" ``` **Check threat feeds:** ```spl | whisperquery query="CALL explain($ip)" params="ip=104.16.123.96" ``` > **Use the macro:** > For a full investigation in one command: `` | `whisper_full_investigation("malware-c2.evil.com")` `` ### Domain to infrastructure Full trace from hostname through DNS, network, and routing layers: ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:RESOLVES_TO]->(ip:IPV4)-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)-[:ROUTES]->(a:ASN)-[:HAS_NAME]->(n:ASN_NAME) RETURN h.name AS hostname, ip.name AS ip, ap.name AS prefix, a.name AS asn, n.name AS asn_name LIMIT 10" params="domain=www.google.com" ``` Or use `whisperlookup` for inline enrichment of events: ```spl index=dns sourcetype=dns | whisperlookup field=query type=domain | table query whisper_ip whisper_prefix whisper_asn whisper_asn_name whisper_country ``` ### IP to ASN ```spl | whisperquery query="MATCH (ip:IPV4 {name: $ip})-[: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 asn_name LIMIT 10" params="ip=8.8.8.8" ``` ### Co-hosted domains Find other domains sharing the same IP address. Low co-hosting density is a common sign of attacker-controlled infrastructure. ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:RESOLVES_TO]->(ip:IPV4)<-[:RESOLVES_TO]-(cohost:HOSTNAME) WHERE cohost.name <> $domain RETURN ip.name AS ip, cohost.name AS cohost LIMIT 100" params="domain=suspicious-site.com" ``` > **Use the macro:** > `` | `whisper_cohosted_domains("suspicious-site.com")` `` ### Shared nameservers Shared nameservers often indicate common ownership or compromised hosting. ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})<-[:NAMESERVER_FOR]-(ns:HOSTNAME)-[:NAMESERVER_FOR]->(other:HOSTNAME) WHERE other.name <> $domain RETURN ns.name AS nameserver, other.name AS shared_domain LIMIT 100" params="domain=phishing-target.com" ``` > **Use the macro:** > `` | `whisper_shared_nameservers("phishing-target.com")` `` ### CNAME chain Follow CNAME alias chains up to 5 hops. Useful for detecting dangling CNAMEs (subdomain takeover risk). ```spl | whisperquery query="MATCH path = (h:HOSTNAME {name: $domain})-[:ALIAS_OF*1..5]->(target:HOSTNAME) RETURN [n IN nodes(path) | n.name] AS chain, last(nodes(path)).name AS target, length(path) AS depth LIMIT 10" params="domain=www.example.com" ``` > **Use the macro:** > `` | `whisper_cname_chain("www.example.com")` `` ### Subdomain discovery Find subdomains using the domain hierarchy index (more efficient than `ENDS WITH`): ```spl | whisperquery query="MATCH (sub:HOSTNAME)-[:CHILD_OF]->(h:HOSTNAME {name: $domain}) RETURN sub.name AS subdomain LIMIT 50" params="domain=google.com" ``` Or by prefix/suffix: ```spl | whisperquery query="MATCH (h:HOSTNAME) WHERE h.name STARTS WITH $prefix RETURN h.name AS hostname LIMIT 50" params="prefix=mail.google" ``` ### SPF include chain Trace SPF includes to audit RFC 7208 compliance (max 10 DNS lookups). ```spl | whisperquery query="MATCH path = (h:HOSTNAME {name: $domain})-[:SPF_INCLUDE*1..3]->(included:HOSTNAME) RETURN [n IN nodes(path) | n.name] AS spf_chain, length(path) AS depth LIMIT 50" params="domain=example.com" ``` **Full SPF mechanism breakdown:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[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" params="domain=microsoft.com" ``` > **SPF_REDIRECT:** > `SPF_REDIRECT` replaces the entire SPF policy with another domain's policy. If the target domain is permissive, your effective policy is too. > **Use the macro:** > `` | `whisper_spf_chain("example.com")` `` ### DNS infrastructure audit **Nameservers:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})<-[:NAMESERVER_FOR]-(ns:HOSTNAME) RETURN ns.name AS nameserver LIMIT 50" params="domain=google.com" ``` **Mail servers:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})<-[:MAIL_FOR]-(mx:HOSTNAME) RETURN mx.name AS mail_server LIMIT 50" params="domain=google.com" ``` **DNSSEC status:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:SIGNED_WITH]->(algo:DNSSEC_ALGORITHM) RETURN h.name AS domain, algo.name AS algorithm LIMIT 1" params="domain=cloudflare.com" ``` ### WHOIS investigation **Current registrar:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:HAS_REGISTRAR]->(r:REGISTRAR) RETURN r.name AS registrar LIMIT 1" params="domain=google.com" ``` **Contact emails:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:HAS_EMAIL]->(e:EMAIL) RETURN e.name AS email LIMIT 10" params="domain=google.com" ``` **Registrant organization:** ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:REGISTERED_BY]->(o:ORGANIZATION) RETURN o.name AS organization LIMIT 5" params="domain=cloudflare.com" ``` **Domain history:** ```spl | whisperquery query="CALL whisper.history($domain)" params="domain=cloudflare.com" ``` ### Attack surface mapping Map all infrastructure behind an ASN: **Routed prefixes:** ```spl | whisperquery query="MATCH (a:ASN {name: $asn})-[:ROUTES]->(p) RETURN a.name AS asn, p.name AS prefix LIMIT 200" params="asn=AS13335" ``` **Hostnames on the ASN's infrastructure:** ```spl | whisperquery query="MATCH (a:ASN {name: $asn})-[:ROUTES]->(p)<-[:BELONGS_TO]-(ip:IPV4)<-[:RESOLVES_TO]-(h:HOSTNAME) RETURN h.name AS hostname, ip.name AS ip LIMIT 50" params="asn=AS13335" ``` **BGP peers:** ```spl | whisperquery query="MATCH (a:ASN {name: $asn})-[:PEERS_WITH]->(peer:ASN)-[:HAS_NAME]->(n:ASN_NAME) RETURN peer.name AS peer_asn, n.name AS peer_name LIMIT 100" params="asn=AS13335" ``` **ASN profile (prefix count + peer count):** ```spl | whisperquery query="MATCH (a:ASN {name: $asn}) 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" params="asn=AS13335" ``` > **Use the macros:** > `` | `whisper_asn_infrastructure("AS13335")` `` and `` | `whisper_bgp_peers("AS13335")` `` ### Threat intelligence **Check if an indicator is listed in any threat feed:** ```spl | whisperquery query="MATCH (n {name: $indicator})-[:LISTED_IN]->(f:FEED_SOURCE) RETURN n.name AS indicator, f.name AS feed_name LIMIT 20" params="indicator=185.220.101.1" ``` **Threat assessment via `explain()`:** ```spl | whisperquery query="CALL explain($indicator)" params="indicator=185.220.101.1" ``` Returns a composite threat score, level (NONE/INFO/LOW/MEDIUM/HIGH/CRITICAL), explanation, contributing factors, and source feed breakdown. Works with IPs, domains, ASNs, CIDR ranges, and file hashes. > **Use the macro:** > `` | `whisper_explain("185.220.101.1")` `` **Threat properties available on enriched nodes:** | Property | Type | Description | |----------|------|-------------| | `threatScore` | Double | Computed composite threat score | | `threatLevel` | String | NONE, INFO, LOW, MEDIUM, HIGH, CRITICAL | | `isThreat` | Boolean | Listed in any threat feed | | `isTor` | Boolean | Tor exit/relay node | | `isC2` | Boolean | Command-and-control infrastructure | | `isMalware` | Boolean | Malware distribution | | `isPhishing` | Boolean | Phishing infrastructure | | `isAnonymizer` | Boolean | Anonymizer/proxy/VPN | These properties are exposed by `whisperlookup` as `whisper_threat_score`, `whisper_threat_level`, etc. ### Batch lookup Enrich a list of indicators in a single query: ```spl | whisperquery query="UNWIND $hosts AS h MATCH (n:HOSTNAME {name: h})-[:RESOLVES_TO]->(ip:IPV4) RETURN n.name AS hostname, ip.name AS ip" params='{"hosts": ["www.google.com", "cloudflare.com", "example.com"]}' ``` ### Web link analysis Find outbound links from a domain (Common Crawl data): ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:LINKS_TO]->(target:HOSTNAME) RETURN target.name AS linked_domain LIMIT 50" params="domain=google.com" ``` ### Campaign infrastructure mapping Find related infrastructure through shared hosting: ```spl | whisperquery query="MATCH (h1:HOSTNAME {name: $domain})-[:RESOLVES_TO]->(ip:IPV4)<-[:RESOLVES_TO]-(h2:HOSTNAME) WHERE h1 <> h2 RETURN h2.name AS related_domain, ip.name AS shared_ip LIMIT 20" params="domain=paypal--confirm.com" ``` Pivot further through shared WHOIS contact emails: ```spl | whisperquery query="MATCH (h1:HOSTNAME {name: $domain})-[:HAS_EMAIL]->(e:EMAIL)<-[:HAS_EMAIL]-(h2:HOSTNAME) WHERE h1 <> h2 RETURN h2.name AS related_domain, e.name AS shared_email LIMIT 20" params="domain=cloudflare.com" ``` > **Note:** > Check whether a shared email is a privacy-service proxy address before drawing attribution conclusions. ### Brand protection Search for domains that impersonate your brand: ```spl | whisperquery query="MATCH (h:HOSTNAME) WHERE h.name CONTAINS $brand RETURN h.name AS hostname LIMIT 20" params="brand=paypal" ``` Then check if any are already threat-listed: ```spl | whisperquery query="MATCH (h:HOSTNAME {name: $domain})-[:LISTED_IN]->(f:FEED_SOURCE) RETURN h.name, f.name" params="domain=paypal--confirm.com" ``` > **Note:** > An empty result means the domain is not yet in any feed, not that it is clean. New phishing domains often predate feed coverage. --- ## Threat intelligence feeds WhisperGraph indexes 39 threat intelligence feeds across 18 categories, with hourly incremental and daily full refresh cycles. ### Feed sources | Feed | Category | |------|----------| | AlienVault Reputation | Reputation | | Binary Defense Banlist | General Blacklists | | Blocklist.de All | General Blacklists | | Blocklist.de Mail | Spam | | Blocklist.de SSH | Brute Force | | Botvrij Domains | Malicious Domains | | Botvrij Dst IPs | C2 Servers | | Brute Force Blocker | Brute Force | | C2 Intel 30d | C2 Servers | | CERT.pl Domains | Malicious Domains | | CINS Score | General Blacklists | | Cloudflare Radar Top 1M | Popularity/Trust | | DNS RD Abuse | General Blacklists | | Dan Tor Exit | TOR Network | | ET Compromised IPs | General Blacklists | | Feodo Tracker | C2 Servers | | FireHOL Abusers 1d | General Blacklists | | FireHOL Anonymous | Proxies | | FireHOL Level 1 | General Blacklists | | FireHOL Level 2 | General Blacklists | | FireHOL Level 3 | General Blacklists | | FireHOL WebClient | General Blacklists | | GreenSnow Blacklist | General Blacklists | | Hagezi Light | Ad/Tracking Blocklists | | Hagezi Pro | Ad/Tracking Blocklists | | IPsum | General Blacklists | | InterServer RBL | General Blacklists | | MalwareBazaar Recent | Malware Distribution | | OpenPhish Feed | Phishing | | SSH Client Attacks | Brute Force | | SSH Password Auth | Brute Force | | SSL IP Blacklist | General Blacklists | | Spamhaus DROP | General Blacklists | | Spamhaus EDROP | General Blacklists | | StevenBlack Hosts | Ad/Tracking Blocklists | | ThreatFox IOCs | C2 Servers | | Tor Exit Nodes | TOR Network | | Tranco Top 1M | Popularity/Trust | | URLhaus Recent | Malware Distribution | ### Categories Ad/Tracking Blocklists, Anonymization Infrastructure, Attack Sources, Brute Force, C2 Servers, General Blacklists, Malicious Domains, Malicious Infrastructure, Malware Distribution, Phishing, Popularity/Trust, Proxies, Reference Data, Reputation, Spam, TOR Network, Threat Intelligence, VPNs. --- ## Query rules | Rule | Reason | |------|--------| | Always anchor with `{name: "value"}` | Prevents full scans across billions of nodes | | Always use `LIMIT` | Controls result set size | | Use `$parameter` syntax with `params=` | Prevents injection, enables index optimization | | Bound variable-length paths (`*1..N`) | Prevents unbounded traversals | | Keep path length within your plan limit | See plan depth limits below | | Use `STARTS WITH` / `ENDS WITH` / `CONTAINS` | Much faster than regex (`=~`) on large labels | | Use `CHILD_OF` for subdomain queries | Indexed edge; faster than `ENDS WITH` | | Use `OPTIONAL MATCH` for WHOIS fields | Avoids losing rows when sparse fields are missing | | Use `ANNOUNCED_BY` for current BGP routing | `BELONGS_TO` returns registered allocation instead | | No write operations | `CREATE`, `DELETE`, `SET`, `MERGE` are blocked; the graph is read-only | | `ENDS WITH` suffix must start with `.` | `.google.com` is indexed; `google.com` requires a full scan | ### API plan depth limits The API enforces traversal depth limits based on your plan. Queries that exceed the limit return a `QueryDepthExceeded` error. For per-plan query depth and quotas, see the [pricing page](https://www.whisper.security/pricing). The pre-built macros and `whisperlookup` queries are set to depths supported by the Free plan or higher (`CNAME *1..5` requires Professional, `SPF_INCLUDE *1..3` requires Free). If you see a depth-exceeded error, either reduce your `*1..N` bound or upgrade your plan. > **Depth-exceeded error:** > A `400 QueryDepthExceeded` error means your query uses a variable-length path that exceeds your plan's hop limit. Reduce the upper bound in `*1..N` or upgrade to a higher plan. ## Performance tips | Query pattern | Typical response | Risk | |---------------|----------------:|:----:| | Anchored lookup `{name: "..."}` | ~96 ms | Low | | `STARTS WITH` / `ENDS WITH` / `CONTAINS` + `LIMIT` | ~97-102 ms | Low | | Single-hop traversal | ~99 ms | Low | | Multi-hop (2-5 hops) | ~98 ms | Low | | Threat intel (`LISTED_IN`, `explain()`) | ~140-182 ms | Low | | `NAMESERVER_FOR` / `MAIL_FOR` traversal | ~438 ms | Medium | | Variable-length path `[*1..3]` | ~123 ms | Medium | | Regex (`=~`) on HOSTNAME | 7 s+ or timeout | Avoid | | Unanchored label scan | ~30 s | Avoid | All timings include network latency (~90-140 ms round trip). ### Best practices - **Anchor your starting node.** `MATCH (n:HOSTNAME {name: "example.com"})` does an indexed lookup. `MATCH (n:HOSTNAME)` scans billions of nodes. - **Always use LIMIT.** Especially on traversals that could fan out (LINKS_TO, RESOLVES_TO on CDN IPs). - **Use OPTIONAL MATCH for WHOIS fields.** Not every domain has a registrar, email, or phone. - **Use count() before pulling large result sets.** Check cardinality first to avoid unexpectedly large responses. - **Use UNWIND for batch lookups.** Pass lists of indicators in a single request rather than one request per indicator. - **Specify edge types explicitly.** `[:RESOLVES_TO]` is faster than `[r]` because the engine does not need to check all edge types. - **Anchor LINKS_TO queries.** The web link graph is one of the largest datasets. Queries without an anchored starting node will time out. | Do this | Not that | Why | |---------|----------|-----| | `MATCH (h:HOSTNAME {name: "example.com"})` | `MATCH (h:HOSTNAME) WHERE h.name = "example.com"` | Inline property gets an indexed lookup | | Always add `LIMIT` | Open-ended traversals | Prevents timeout on billion-scale labels | | `OPTIONAL MATCH` for WHOIS fields | `MATCH` for sparse relationships | Avoids losing rows when fields are missing | | `MATCH (sub)-[:CHILD_OF]->(h {name: "x.com"})` | `WHERE h.name ENDS WITH ".x.com"` | CHILD_OF uses an indexed edge; ENDS WITH scans | > **Queries to avoid:** > - `MATCH (n:HOSTNAME) RETURN n.name` without `LIMIT` -- scans billions of rows > - `ORDER BY` on unfiltered large scans -- sorts billions of rows > - `=~` regex on HOSTNAME -- full scan, may timeout > - Unbounded `[*]` without `LIMIT` -- explosive traversal --- ### Enterprise Security Integration Markdown: https://www.whisper.security/docs/integrations/splunk/es-integration.md HTML: https://www.whisper.security/docs/integrations/splunk/es-integration The Whisper Security Add-on is Splunk-ES-compatible but does not ship a prebuilt correlation-search pack. What you get out of the box is - two ES Threat Intelligence KV Store collections (`whisper_ip_intel`, `whisper_domain_intel`) plus disabled-by-default populator searches, - an **Enrich with Whisper** adaptive response action usable from ES notable events, - CIM-compliant field aliases on `whisper:enrichment` events so notable events and RBA can correlate against Network Resolution and Threat Intelligence data models, - nine `whisper_*` graph-query macros you can call from your own detections, plus four disabled-by-default enrichment pipeline templates you can clone into detections or risk generators. This page documents what ships, and then walks through how to build your own detections and RBA rules using the pieces above. ## What ships ### Threat Intelligence framework integration The ES Threat Intelligence framework consumes KV Store collections that match the ES `ip_intel` and `domain_intel` schemas. The add-on ships two: | Collection | Matches ES schema | Populated by | |------------|-------------------|--------------| | `whisper_ip_intel` | `ip_intel` | `Whisper - Populate IP Threat Intel KV Store` (disabled) | | `whisper_domain_intel` | `domain_intel` | `Whisper - Populate Domain Threat Intel KV Store` (disabled) | Both populator searches live in `savedsearches.conf` and are disabled by default (AppInspect requirement). Each search queries `MATCH ... LISTED_IN ... FEED_SOURCE` against the WhisperGraph, `outputlookup`s to the collection, and tags each record with `threat_category = whisper` so ES can surface them as a feed source. The `_time` field on each record uses the indicator's latest `lastSeen` timestamp from its threat feed sources when available, which reflects when the indicator was most recently observed as active -- more useful for ES correlation than collection time. When no source timestamps are available (e.g. indicators not listed in any feed), `_time` falls back to the collection time. Records are written using KV Store `batch_save` (up to 1000 per batch) for efficient bulk population. If a batch fails, the populator falls back to per-record inserts to maximise coverage. #### Collection schemas **`whisper_ip_intel`** (ES `ip_intel`-compatible): | Field | Type | Description | |-------|------|-------------| | `ip` | string | IP address (lookup key) | | `threat_collection_name` | string | ES threat intelligence collection name this record belongs to (used by the ES threat framework to namespace indicators) | | `threat_collection_key` | string | ES threat intelligence collection key (identifier within the collection) | | `description` | string | Threat description | | `threat_key` | string | Threat classification | | `threat_group` | string | `whisper_graph` | | `weight` | number | ES threat weight: 1 (low), 2 (medium), 3 (high) | | `whisper_asn` | string | ASN | | `whisper_asn_name` | string | ASN organisation name | | `whisper_country` | string | Country code | | `whisper_prefix` | string | IP prefix (CIDR) | | `whisper_risk_score` | number | Normalised risk score (0-100) | | `whisper_risk_level` | string | `informational` / `low` / `medium` / `high` / `critical` | | `whisper_threat_score` | number | Raw threat score from `CALL explain()` | | `whisper_threat_level` | string | Threat level from `CALL explain()` | **`whisper_domain_intel`** (ES `domain_intel`-compatible): | Field | Type | Description | |-------|------|-------------| | `domain` | string | Domain name (lookup key) | | `threat_collection_name` | string | ES threat intelligence collection name this record belongs to | | `threat_collection_key` | string | ES threat intelligence collection key (identifier within the collection) | | `description` | string | Threat description | | `threat_key` | string | Threat classification | | `threat_group` | string | `whisper_graph` | | `weight` | number | ES threat weight: 1 / 2 / 3 | | `whisper_asn_name` | string | ASN organisation name | | `whisper_country` | string | Country code | | `whisper_risk_score` | number | Normalised risk score (0-100) | | `whisper_risk_level` | string | Risk level | | `whisper_threat_score` | number | Raw threat score | | `whisper_threat_level` | string | Threat level | #### Enabling the populators In Splunk: 1. Go to **Settings > Searches, Reports, and Alerts**. 2. Filter on **app = TA-whisper-graph**. 3. Edit **Whisper - Populate IP Threat Intel KV Store** and **Whisper - Populate Domain Threat Intel KV Store** -- set **Enabled = true** and pick a schedule (the default `0 */6 * * *` runs every 6 hours). 4. In ES, confirm the feeds appear under **Configure > Data Enrichment > Threat Intelligence Management**. ### Enrich with Whisper adaptive response action `alert_actions.conf` ships the `[whisper_enrich]` custom alert action backed by `whisper_adaptive_response.py`. This lets an ES analyst (or a correlation search) enrich an indicator on demand and write the result back onto the notable event. From a notable event: 1. Open the notable in **Incident Review**. 2. **Actions > Run Adaptive Response > Enrich with Whisper**. 3. The action pulls candidate indicators from `src`, `dest`, `src_dns`, and `dest_dns` on the notable, calls the WhisperGraph, and adds the enrichment result as a comment on the notable. From a correlation search, attach `action.whisper_enrich = 1` to the saved search to fire the enrichment automatically on each triggered result. ### CIM field aliases on enrichment events `props.conf` aliases Whisper-prefixed fields on `whisper:enrichment` events to CIM field names so notable events, RBA rules, and ES dashboards can correlate without custom extractions. The full alias table is in [CIM Mapping](https://www.whisper.security/docs/integrations/splunk/cim-mapping.md), but at a glance: - `whisper_ip` -> `dest_ip` - `whisper_country` -> `dest_country` - `whisper_asn` -> `dest_asn` - `whisper_threat_score` -> `threat_score`, `whisper_threat_level` -> `threat_level` - `whisper_risk_score` -> `risk_score`, `whisper_risk_level` -> `risk_level` - all 13 `whisper_is_*` boolean threat flags alias to the CIM Threat Intelligence equivalents (`is_c2`, `is_malware`, `is_phishing`, ...) - `vendor` and `vendor_product` are set via `EVAL` so CIM dashboards group Whisper data under a consistent vendor label. The events are tagged `network resolution dns`, so they participate in the Network Resolution and DNS CIM data models. `whisper:threat_intel` and `whisper:watchlist` events are tagged `threat report` for Threat Intelligence. ### Example enrichment pipeline templates `savedsearches.conf` ships four **disabled** enrichment pipeline templates under the `Example - Whisper - ...` prefix. Each is a starting point: clone it, swap the placeholders, pick a destination index, and enable: | Template | What it does | |----------|--------------| | `Example - Whisper - Enrich DNS Domains` | Enrich DNS `query` values with `whisperlookup`, write to `` as `sourcetype=whisper:enriched_dns` | | `Example - Whisper - Enrich Destination IPs` | Enrich `dest_ip` from network traffic, write as `sourcetype=whisper:enriched_ip` | | `Example - Whisper - Enrich Proxy Hostnames` | Enrich `url_domain` from proxy logs, write as `sourcetype=whisper:enriched_proxy` | | `Example - Whisper - Custom Graph Query Enrichment` | Run an arbitrary Cypher query per indicator via `whisperquery`, write as `sourcetype=whisper:enriched_custom` | Each template's `search` field uses ``, ``, ``, and `` placeholders that you must replace before enabling. ### Graph-query macros `macros.conf` ships nine macros you can reuse inside detections, dashboards, or RBA rules to keep SPL short and consistent. They are query helpers, not correlation-search thresholds: | Macro | Arguments | Purpose | |-------|-----------|---------| | `whisper_index` | -- | Index override used by every shipped dashboard/search (default `index=whisper`) | | `whisper_shared_nameservers(domain)` | domain | Domains sharing nameservers with the given domain | | `whisper_asn_infrastructure(asn)` | asn | Prefixes routed by an ASN | | `whisper_cname_chain(domain)` | domain | CNAME chain resolution up to 5 hops | | `whisper_spf_chain(domain)` | domain | SPF include chain up to 3 hops | | `whisper_bgp_peers(asn)` | asn | BGP peers of an ASN with name + country | | `whisper_cohosted_domains(domain)` | domain | Domains co-hosted on the same IP | | `whisper_full_investigation(indicator)` | indicator | hostname -> IP -> ASN with geo and reverse DNS | | `whisper_explain(indicator)` | indicator | Threat assessment via `CALL explain()` | All macros are parameterised -- the `$domain$` / `$asn$` / `$indicator$` substitutions flow into the `params` argument of `whisperquery`, so there is no string interpolation on the way into the Cypher query. ## Building your own detections The recommended recipe for a Whisper-powered ES detection: 1. **Pick a source index** -- network traffic, proxy, DNS, or whichever events contain the indicator you want to enrich. 2. **Clone one of the example templates** as a starting point, or write a fresh stanza if none fits. 3. **Call `whisperlookup` (for enrichment) or one of the `whisper_*` macros (for graph traversal)** to add Whisper context. 4. **Add your detection condition** as a `where` clause on `whisper_risk_score`, `whisper_threat_level`, one of the `whisper_is_*` flags, or a graph-traversal result. 5. **Decide what happens on match** -- write to `index=risk` for RBA, emit an `index=notable` event, or route back into your security workflow. 6. **Schedule it disabled**, tune it against historical data, then enable. ### Worked example 1 -- suspicious CNAME chain Goal: flag DNS queries where the CNAME chain is longer than 3 hops and the chain does not terminate at a known CDN. ```spl index= sourcetype= earliest=-15m query=* | rename query AS indicator | dedup indicator | `whisper_cname_chain(indicator)` | where depth > 3 | lookup whisper_cdn_asns.csv asn AS whisper_asn OUTPUT asn AS cdn_asn | where isnull(cdn_asn) | eval risk_score=30 | eval risk_message="Whisper: suspicious CNAME chain depth=" . depth . " ending at " . cname_target | eval risk_object=indicator, risk_object_type="other" | collect index=risk sourcetype=whisper:risk ``` The `depth` threshold (`3`) lives in the SPL -- override it there, not in a macro. `whisper_cdn_asns.csv` ships in `lookups/` and lists known CDN/SaaS ASNs for noise reduction. ### Worked example 2 -- multi-feed threat IP communication Goal: flag internal hosts talking to IPs appearing on multiple distinct threat feeds (high-confidence threat signal). ```spl index= sourcetype= earliest=-15m action=allowed | rename dest_ip AS indicator | dedup src_ip indicator | `whisper_explain(indicator)` | spath threatFeeds{} output=feeds | eval feed_count=mvcount(feeds) | where feed_count >= 2 | eval risk_score=case(feed_count >= 4, 80, feed_count >= 2, 50, 1=1, 0) | eval risk_message="Whisper: destination IP listed on " . feed_count . " threat feeds" | eval risk_object=src_ip, risk_object_type="system" | collect index=risk sourcetype=whisper:risk ``` The `>= 2` minimum feed count is the detection threshold -- change it inline. ### Worked example 3 -- co-hosting density anomaly Goal: flag outbound traffic to IPs with fewer than 5 co-hosted domains (dedicated infrastructure pattern often used by C2 or phishing), while excluding known CDN ASNs. ```spl index= sourcetype= earliest=-30m action=allowed | rename dest_ip AS indicator | dedup src_ip indicator | whisperlookup field=indicator type=ip | where whisper_cohosting_count < 5 | lookup whisper_cdn_asns.csv asn AS whisper_asn OUTPUT asn AS cdn_asn | where isnull(cdn_asn) | eval risk_score=25 | eval risk_message="Whisper: low-density cohosting (" . whisper_cohosting_count . " domains on " . indicator . ")" | eval risk_object=src_ip, risk_object_type="system" | collect index=risk sourcetype=whisper:risk ``` ### Worked example 4 -- BGP prefix conflict Goal: monitor an inventory of your organisation's ASNs for prefix conflicts (i.e. prefixes announced by unexpected ASNs -- a BGP hijack signal). You will need a CSV of your ASNs (e.g. a hand-maintained `my_org_asns.csv`): ```spl | inputlookup my_org_asns.csv | rename asn AS our_asn | map search="| whisperquery query=\"MATCH (a:ASN {name: $our_asn$})-[:ROUTES]->(p:PREFIX)<-[:CONFLICTS_WITH]-(other_p:PREFIX)<-[:ROUTES]-(other:ASN) WHERE other.name <> $our_asn$ RETURN a.name AS our_asn, p.name AS prefix, other.name AS conflicting_asn LIMIT 50\" params=\"our_asn=$our_asn$\"" | eval risk_score=75 | eval risk_message="Whisper: prefix " . prefix . " announced by our ASN " . our_asn . " and unexpected ASN " . conflicting_asn | eval risk_object=conflicting_asn, risk_object_type="other" | collect index=risk sourcetype=whisper:risk ``` `| map` is used so each `our_asn` is passed through as a parameter rather than interpolated into the Cypher string. ### Inline risk scores If you do not want to write a detection and only want to react to Whisper-computed risk scores, every `whisperlookup` result already includes four risk fields you can alert on: | Field | Description | |-------|-------------| | `whisper_risk_score` | Normalised 0-100, aliased to CIM `risk_score` | | `whisper_risk_level` | `informational` / `low` / `medium` / `high` / `critical` | | `whisper_risk_factors_list` | Comma-separated list of contributing factors | | `whisper_risk_components` | JSON with per-factor score + detail | Tune the factor weights in `lookups/whisper_risk_factors.csv` -- each row specifies a factor name, point value, and description. Use **Settings > Lookups > Lookup table files** to edit the CSV without touching `.conf` files. See the [Enrichment page](https://www.whisper.security/docs/integrations/splunk/enrichment.md) for the full factor list and weights. Example RBA trigger based on inline risk scores: ```spl index= sourcetype= earliest=-15m | rename AS indicator | dedup indicator | whisperlookup field=indicator | where whisper_risk_score >= 60 | eval risk_score=whisper_risk_score | eval risk_message="Whisper risk " . whisper_risk_level . " (" . whisper_risk_factors_list . ")" | eval risk_object=indicator, risk_object_type=if(whisper_indicator_type=="ip", "system", "other") | collect index=risk sourcetype=whisper:risk ``` ## Deployment notes for ES environments - Install the add-on on the ES search head (or the ES search head cluster). The adaptive response action and threat intel populator searches run on the search head. - If you operate a separate heavy forwarder for data collection, the modular inputs can also be configured there -- see the [Modular Inputs page](https://www.whisper.security/docs/integrations/splunk/modular-inputs.md). - Confirm `whisper_user` is imported (via `authorize.conf`) into the ES roles your analysts use so they can run `whisperlookup` / `whisperquery` from the ES search bar. - Add `TA-whisper-graph` to your ES deployment-server bundle if shipping to a distributed environment; see the [Deployment Architecture page](https://www.whisper.security/docs/integrations/splunk/deployment-architecture.md). ## Related pages - [Saved Searches](https://www.whisper.security/docs/integrations/splunk/saved-searches.md) -- full list of shipping utility and example searches. - [Macros](https://www.whisper.security/docs/integrations/splunk/macros.md) -- every `whisper_*` graph macro with parameters and example output. - [CIM Mapping](https://www.whisper.security/docs/integrations/splunk/cim-mapping.md) -- full CIM alias table and data-model coverage. - [Enrichment](https://www.whisper.security/docs/integrations/splunk/enrichment.md) -- risk factor list and scoring engine. - [Search Commands](https://www.whisper.security/docs/integrations/splunk/search-commands.md) -- `whisperlookup`, `whisperquery`, and the rest of the command set. --- ### CIM Mapping Markdown: https://www.whisper.security/docs/integrations/splunk/cim-mapping.md HTML: https://www.whisper.security/docs/integrations/splunk/cim-mapping ## Overview The Whisper Security Add-on maps enrichment fields to Splunk's [Common Information Model (CIM)](https://docs.splunk.com/Documentation/CIM/latest/User/Overview) using `FIELDALIAS` definitions in `props.conf` and tag assignments in `tags.conf`. This enables compatibility with CIM-based dashboards, reports, and Enterprise Security. ## Data model mappings ### Network Resolution (DNS) **Source type:** `whisper:enrichment` **Event type:** `whisper_enrichment` **Tags:** `network`, `resolution`, `dns` | Whisper field | CIM field | Description | |--------------|-----------|-------------| | `whisper_ip` | `dest_ip` | Destination IP address | | `whisper_country` | `dest_country` | Destination country code | | `whisper_asn` | `dest_asn` | Destination Autonomous System Number | ### Threat Intelligence **Source type:** `whisper:enrichment` **Event type:** `whisper_enrichment` | Whisper field | CIM field | Description | |--------------|-----------|-------------| | `whisper_threat_score` | `threat_score` | Numeric threat score (0-100+) | | `whisper_threat_level` | `threat_level` | Threat severity level | | `whisper_is_threat` | `is_threat` | Known threat indicator | | `whisper_is_c2` | `is_c2` | Command-and-control server | | `whisper_is_tor` | `is_tor` | Tor exit node | | `whisper_is_malware` | `is_malware` | Malware distribution | | `whisper_is_phishing` | `is_phishing` | Phishing host | | `whisper_is_anonymizer` | `is_anonymizer` | Anonymization service | | `whisper_is_spam` | `is_spam` | Spam source | | `whisper_is_bruteforce` | `is_bruteforce` | Brute-force source | | `whisper_is_scanner` | `is_scanner` | Network scanner | | `whisper_is_blacklist` | `is_blacklist` | Public blacklist entry | | `whisper_is_proxy` | `is_proxy` | Open proxy | | `whisper_is_vpn` | `is_vpn` | Known VPN exit | | `whisper_is_whitelist` | `is_whitelist` | Explicitly whitelisted | | `whisper_risk_score` | `risk_score` | Normalized risk score (0-100) | | `whisper_risk_level` | `risk_level` | Risk level classification | ### Computed fields These fields are set automatically on all `whisper:enrichment` events via `EVAL` in `props.conf`: | Field | Value | Purpose | |-------|-------|---------| | `vendor` | `Whisper Security` | CIM vendor identification | | `vendor_product` | `Whisper Knowledge Graph` | CIM product identification | ## Event type and tag reference | Event type | Source type | Tags | CIM data models | |-----------|------------|------|----------------| | `whisper_enrichment` | `whisper:enrichment` | `network`, `resolution`, `dns` | Network Resolution, DNS | | `whisper_threat_intel` | `whisper:threat_intel` | `threat`, `report` | Threat Intelligence | | `whisper_watchlist` | `whisper:watchlist` | `threat`, `report` | Threat Intelligence | | -- (no event type) | `whisper:attack_surface` | -- | Not CIM-normalised | > **whisper:attack_surface is not CIM-normalised:** > `whisper:attack_surface` events describe DNS/WHOIS/ASN posture for a > monitored domain (domain, record_type, record_value, collection_id). > They are not network connections -- there is no `src_ip` / `dest_ip` > / `transport` / `bytes` pattern -- so the add-on does not claim > Network Traffic or any other CIM data-model compliance for this > sourcetype. Query it using native field names > (`domain`, `record_type`, `record_value`), and see > [Attack Surface Monitoring](https://www.whisper.security/docs/integrations/splunk/modular-inputs.md) for the > shipping dashboards that consume it. ## Field alias configuration All field aliases are defined in `default/props.conf` under the `[whisper:enrichment]` stanza: ```ini [whisper:enrichment] FIELDALIAS-whisper_dest_ip = whisper_ip AS dest_ip FIELDALIAS-whisper_dest_country = whisper_country AS dest_country FIELDALIAS-whisper_dest_asn = whisper_asn AS dest_asn FIELDALIAS-whisper_threat_score = whisper_threat_score AS threat_score FIELDALIAS-whisper_threat_level = whisper_threat_level AS threat_level FIELDALIAS-whisper_is_threat = whisper_is_threat AS is_threat ... EVAL-vendor = "Whisper Security" EVAL-vendor_product = "Whisper Knowledge Graph" ``` > **Field coexistence:** > Both the `whisper_` prefixed field and the CIM alias exist simultaneously on each event. Use `whisper_` prefixed fields for Whisper-specific queries and CIM fields for cross-vendor dashboards and data model searches. ## Compliance field aliases | Source type | Whisper field | CIM field | |------------|--------------|-----------| | `whisper:spf_compliance` | `collected_at` | `last_checked` | ## ES threat intel collection schema The threat intelligence modular input populates ES-compatible KV Store collections: ### whisper_ip_intel Compatible with the ES `ip_intel` lookup. Fields: | Field | Type | Description | |-------|------|-------------| | `ip` | string | IP address (lookup key) | | `description` | string | Threat description | | `threat_key` | string | Threat classification | | `threat_group` | string | Threat group attribution | | `weight` | number | ES threat weight: 1 (low), 2 (medium), 3 (high) | | `whisper_asn` | string | ASN | | `whisper_asn_name` | string | ASN organization | | `whisper_country` | string | Country code | | `whisper_prefix` | string | IP prefix | | `whisper_risk_score` | number | Risk score (0-100) | | `whisper_risk_level` | string | Risk level | | `whisper_threat_score` | number | Raw threat score | | `whisper_threat_level` | string | Threat level | ### whisper_domain_intel Compatible with the ES `domain_intel` lookup. Fields: | Field | Type | Description | |-------|------|-------------| | `domain` | string | Domain name (lookup key) | | `description` | string | Threat description | | `threat_key` | string | Threat classification | | `threat_group` | string | Threat group attribution | | `weight` | number | ES threat weight | | `whisper_asn_name` | string | ASN organization | | `whisper_country` | string | Country code | | `whisper_risk_score` | number | Risk score (0-100) | | `whisper_risk_level` | string | Risk level | | `whisper_threat_score` | number | Raw threat score | | `whisper_threat_level` | string | Threat level | ## Validating CIM compliance Search for events that match CIM data model tags: ```spl tag=network tag=resolution tag=dns | head 10 | table _time dest_ip dest_country dest_asn vendor vendor_product ``` Check that CIM field aliases are populated: ```spl sourcetype=whisper:enrichment | head 10 | table whisper_ip dest_ip whisper_country dest_country whisper_asn dest_asn ``` --- ### Source Types Markdown: https://www.whisper.security/docs/integrations/splunk/source-types.md HTML: https://www.whisper.security/docs/integrations/splunk/source-types ## Overview The Whisper Security Add-on produces events with the following source types. All events are written to the index configured in the modular input settings (default: `whisper` for data events, `_internal` for operational events). ## Source type reference ### whisper:enrichment Enrichment results from the `whisperlookup` streaming command or the watchlist enrichment input. | Property | Value | |----------|-------| | **Index** | Configured per input (default: `whisper`) | | **Generated by** | `whisperlookup` command, watchlist enrichment input | | **CIM models** | Network Resolution, DNS, Threat Intelligence | | **CIM tags** | `network`, `resolution`, `dns` | | **Event type** | `whisper_enrichment` | **Key fields:** | Field | Type | Description | |-------|------|-------------| | `whisper_ip` | string | Resolved IP address | | `whisper_asn` | string | Autonomous System Number | | `whisper_asn_name` | string | ASN organization name | | `whisper_country` | string | Country code | | `whisper_prefix` | string | IP prefix (CIDR notation) | | `whisper_threat_score` | number | Threat score (0-100+) | | `whisper_threat_level` | string | NONE / LOW / MEDIUM / HIGH / CRITICAL | | `vendor` | string | "Whisper Security" (computed) | | `vendor_product` | string | "Whisper Knowledge Graph" (computed) | See the [Enrichment](https://www.whisper.security/docs/integrations/splunk/enrichment.md) page for the full field list (66+ fields). **Example event:** ``` indicator=example.com indicator_type=domain whisper_ip=93.184.216.34 whisper_asn=AS15133 whisper_asn_name="Edgecast Inc." whisper_country=US whisper_threat_level=NONE whisper_threat_score=0 ``` --- ### whisper:threat_intel Threat intelligence collection events from the threat intel modular input. | Property | Value | |----------|-------| | **Index** | `_internal` | | **Generated by** | Whisper Threat Intelligence input | | **CIM models** | Threat Intelligence | | **CIM tags** | `threat`, `report` | | **Event type** | `whisper_threat_intel` | **Key fields:** | Field | Type | Description | |-------|------|-------------| | `indicator` | string | IP or domain indicator | | `indicator_type` | string | `ip` or `domain` | | `threat_score` | number | Threat score from explain API | | `threat_level` | string | NONE / LOW / MEDIUM / HIGH / CRITICAL | --- ### whisper:attack_surface DNS baseline collection events from the DNS baseline modular input. | Property | Value | |----------|-------| | **Index** | Configured per input (default: `whisper`) | | **Generated by** | Whisper DNS Baseline input | | **CIM models** | -- (not CIM-normalised; query by native field names) | | **CIM tags** | -- | | **Event type** | -- | **Key fields:** | Field | Type | Description | |-------|------|-------------| | `domain` | string | Monitored domain | | `record_type` | string | DNS record type: `A`, `NS`, `MX`, `CNAME`, `subdomain` | | `record_value` | string | DNS record value | | `collection_id` | string | Unique baseline collection identifier | **Example event:** ``` domain=example.com record_type=A record_value=93.184.216.34 collection_id=20260401T120000Z ``` --- ### whisper:watchlist Pre-computed enrichment events from the watchlist modular input. | Property | Value | |----------|-------| | **Index** | Configured per input (default: `whisper`) | | **Generated by** | Whisper Watchlist Enrichment input | | **CIM models** | Threat Intelligence | | **CIM tags** | `threat`, `report` | | **Event type** | `whisper_watchlist` | **Key fields:** Same as `whisper:enrichment`. --- ### whisper:spf_compliance SPF compliance check results from the compliance query input. | Property | Value | |----------|-------| | **Index** | Configured per input (default: `whisper`) | | **Generated by** | Whisper Compliance Queries input | | **CIM models** | -- | | **CIM tags** | -- | | **Field alias** | `collected_at` aliased to `last_checked` | ## Index mapping | Source type | Default index | Purpose | |------------|---------------|---------| | `whisper:enrichment` | `whisper` | Enrichment results | | `whisper:threat_intel` | `_internal` | Threat intel collection logs | | `whisper:attack_surface` | `whisper` | DNS baseline data | | `whisper:watchlist` | `whisper` | Pre-computed enrichments | | `whisper:spf_compliance` | `whisper` | SPF compliance data | | `ta_whisper_graph` | `_internal` | TA operational logs (UCC framework) | > **Custom index:** > All dashboards reference the `whisper_index` macro (default: `index=whisper`). Override this macro to use a different index for data events. --- ### Troubleshooting Markdown: https://www.whisper.security/docs/integrations/splunk/troubleshooting.md HTML: https://www.whisper.security/docs/integrations/splunk/troubleshooting ## Quick checks Run these first when something looks off. Check API connectivity: ```spl | whisperquery query="RETURN 1 AS test LIMIT 1" ``` If this returns no result the add-on is not reaching the API. Confirm the app is installed and enabled: ```spl | rest /services/apps/local/TA-whisper-graph | table label version disabled ``` Check for log activity: ```spl index=_internal sourcetype="ta_whisper_graph" | stats count by log_level ``` Zero rows here means no add-on component has run yet, or you are searching the wrong time range. ## Common errors ### `WhisperAPIRequestError: Whisper API error 401` The API key is invalid or missing. Re-check it under **Apps > Whisper Security TA > Configuration > Account** and use **Test Connectivity**. The button reports the plan tier; a tier mismatch means the key was rejected silently. ### `whisperquery: Query validation failed: query must include LIMIT clause` Cypher queries must include a `LIMIT N` clause. This is a guardrail to keep search-time queries bounded. ### Test Connectivity reports an unrecognised key The API accepted your request but did not recognise the key, so it fell back to a lower tier. Common causes: trailing whitespace pasted in with the key, a key from a different environment, or a key that has expired. Re-copy from [console.whisper.security](https://console.whisper.security) and paste it again. ### App shows "not fully configured" after saving the account The `is_configured` flag in `local/app.conf` was not flipped by the UCC setup hook. Save the account again from the Configuration page (the hook will retry). If it still does not clear, set the flag manually: ```bash curl -k -u admin: \ https://localhost:8089/servicesNS/nobody/TA-whisper-graph/configs/conf-app/install \ -d is_configured=true ``` Refresh the browser. ### `whisperflush` returns a permission error The `whisperflush` command requires `admin` (Enterprise) or `sc_admin` (Cloud) capabilities. Run it as a user with one of those roles, or have an admin run it for you. ### Enrichment returns stale data Flush the cache. The default TTL is one hour: ```spl | whisperflush collection=cache ``` ### Modular inputs not collecting data on Splunk Cloud Classic Classic uses an event-based pipeline because the IDM cannot write to KV Store directly. Confirm: 1. Modular inputs are enabled on the IDM. 2. Events are landing: `index=whisper sourcetype=whisper:threat_intel`. 3. The disabled-by-default populator saved searches (`Whisper - Populate IP Threat Intel KV Store`, `Whisper - Populate Domain Threat Intel KV Store`, `Whisper - Populate Precomputed Enrichment KV Store`) are enabled and scheduled to run after the modular-input collection interval. ### KV Store not replicating across SHC members Verify replication status: ```spl | rest /services/kvstore/status | table title currentStatus replicationStatus ``` Confirm `server.conf` includes `[shclustering] conf_replication_include.ta_whisper_graph_settings = true` (the TA ships this). If it is missing on one member, push the SHC bundle from the deployer again. ## Where the logs live Operational logs go to the `_internal` index under sourcetype `ta_whisper_graph` (the UCC default). Do not confuse this with the `whisper:*` sourcetypes used for indexed data. ```spl index=_internal sourcetype="ta_whisper_graph" log_level=ERROR | table _time component message | sort -_time ``` On disk: ```bash ## Cloud Victoria + recent Enterprise / Cloud Classic tail -f $SPLUNK_HOME/var/log/splunk/TA-whisper-graph/ta_whisper_graph.log ## Older deployments (fallback path) tail -f $SPLUNK_HOME/var/log/splunk/ta_whisper_graph.log ``` To raise log verbosity for diagnosis, go to **Settings > Server Settings > Server Logging**, search for `whisper`, and set the level to DEBUG. Set it back to INFO when you are done — DEBUG is chatty. ## File-precedence gotchas Splunk merges configuration from `default/` (shipped) and `local/` (admin overrides), with `local/` winning. Safe to override in `local/`: * `macros.conf` — thresholds, time ranges, the `whisper_index` macro * `savedsearches.conf` — enable/disable, schedules, alert conditions * `inputs.conf` — modular input intervals, target index per input * `authorize.conf` — `whisper_user` role tweaks Do **not** override these. You will break command registration, KV Store schemas, CIM aliases, or app metadata: * `commands.conf` * `transforms.conf` * `collections.conf` * `props.conf` * `app.conf` ## Collecting logs for support Before opening a ticket, gather: ```spl index=_internal source=*ta_whisper_graph.log earliest=-24h | table _time log_level component message | sort -_time ``` ```spl index=_internal source=*splunkd.log ExecProcessor whisper earliest=-24h | table _time log_level message ``` ```bash $SPLUNK_HOME/bin/splunk diag --collect app:TA-whisper-graph ``` The diag bundle includes app config, logs, and KV Store metadata, but not credentials or customer data. ## Getting help * Support portal: [console.whisper.security/support](https://console.whisper.security/support) --- ## Known limitations For a list of platform-wide gaps (anycast GeoIP, threat-feed staleness, etc.) see [Known Limitations](https://www.whisper.security/docs/known-limitations.md). Splunk-specific: - **No prebuilt detection pack ships.** Saved searches are disabled-by-default templates customers clone. See [Saved Searches](https://www.whisper.security/docs/integrations/splunk/saved-searches.md). - **ES integration is opt-in.** KV-store populators and RBA hooks are disabled by default. See [Enterprise Security Integration](https://www.whisper.security/docs/integrations/splunk/es-integration.md). - **AppInspect compliance.** The TA passes Splunk Cloud AppInspect; some advanced features (custom python libraries, native binaries) cannot be added without re-certification. ## Blog Posts Article titles, descriptions, and both citation URLs (markdown-first). For full article bodies, fetch the `.md` URL. - Your AI agent is only as smart as what it can see: AI agents in security lack context, not data. How MCP and graph-based infrastructure intelligence enable agents to reason over connected data. Markdown: https://www.whisper.security/resources/blog/your-ai-agent-is-only-as-smart-as-what-it-can-see.md HTML: https://www.whisper.security/resources/blog/your-ai-agent-is-only-as-smart-as-what-it-can-see - Concentration risk: when half the internet depends on three companies: Most organizations overlook shared infrastructure dependencies across cloud, DNS, and ASNs. Learn how to identify and quantify concentration risk before. Markdown: https://www.whisper.security/resources/blog/cloud-concentration-risk-and-hidden-dependencies.md HTML: https://www.whisper.security/resources/blog/cloud-concentration-risk-and-hidden-dependencies - The Internet Has a Map. Most Defenders Can't Read It.: The internet’s infrastructure is public but fragmented. Learn how graph intelligence connects DNS, BGP, WHOIS, and certificates to reveal hidden threat. Markdown: https://www.whisper.security/resources/blog/mapping-internet-infrastructure-with-graph-intelligence.md HTML: https://www.whisper.security/resources/blog/mapping-internet-infrastructure-with-graph-intelligence - Why blocklists are always one step behind: Blocklists react to attacks after they happen. Learn how infrastructure intelligence and graph analysis reveal phishing campaigns before new domains go. Markdown: https://www.whisper.security/resources/blog/why-blocklists-are-always-one-step-behind.md HTML: https://www.whisper.security/resources/blog/why-blocklists-are-always-one-step-behind - One Phishing Domain Is Never Just One: Detect phishing campaigns by mapping shared infrastructure, domain clusters, DNS patterns, and hosting relationships using graph intelligence Markdown: https://www.whisper.security/resources/blog/one-phishing-domain-is-never-just-one.md HTML: https://www.whisper.security/resources/blog/one-phishing-domain-is-never-just-one - Your Vendors' Infrastructure Is Your Attack Surface Too: Your firewall isn't your perimeter — your vendors are. Learn how Whisper reveals hidden attack surface risks across third-party infrastructure. Try it. Markdown: https://www.whisper.security/resources/blog/vendor-infrastructure-is-your-attack-surface.md HTML: https://www.whisper.security/resources/blog/vendor-infrastructure-is-your-attack-surface - Attackers Change Their Domains. They Rarely Change Their Habits.: Attackers rotate domains daily but their infrastructure habits persist. Learn how infrastructure fingerprinting detects threats before they strike. Try. Markdown: https://www.whisper.security/resources/blog/attackers-change-domains-not-habits.md HTML: https://www.whisper.security/resources/blog/attackers-change-domains-not-habits - Rahul Saggar Joins Whisper as CRO: Whisper welcomes Rahul B Saggar as CRO, strengthening its founding team to scale commercial growth and accelerate its cybersecurity vision. Markdown: https://www.whisper.security/resources/blog/rahul-saggar-joins-whisper-as-cro.md HTML: https://www.whisper.security/resources/blog/rahul-saggar-joins-whisper-as-cro - Whisper is Coming to WebSummit 2025: Whisper will exhibit at WebSummit 2025 in Lisbon, showcasing its predictive cybersecurity platform and live 'God Mode' internet threat intelligence demos. Markdown: https://www.whisper.security/resources/blog/whisper-is-coming-to-websummit-2025.md HTML: https://www.whisper.security/resources/blog/whisper-is-coming-to-websummit-2025 - Inside the D11Z Academy: Learning, Growing, and Building Together: Whisper attended D11Z Academy in Heilbronn, gaining practical insights, sharing experiences with fellow founders, and strengthening investor relationships. Markdown: https://www.whisper.security/resources/blog/inside-the-d11z-academy-learning-growing-and-building-together.md HTML: https://www.whisper.security/resources/blog/inside-the-d11z-academy-learning-growing-and-building-together - Whisper Welcomes Roman Viliavin!: Whisper welcomes Roman Viliavin as COO, strengthening its founding team with proven operational leadership to scale growth and cybersecurity innovation. Markdown: https://www.whisper.security/resources/blog/whisper-welcomes-roman-viliavin.md HTML: https://www.whisper.security/resources/blog/whisper-welcomes-roman-viliavin - Whisper Secures €1.6M in Pre-Seed Funding to Revolutionize Predictive Cybersecurity: Whisper raised €1.6 million pre-seed funding to expand its predictive cybersecurity platform that detects and stops cyber threats proactively. Markdown: https://www.whisper.security/resources/blog/whisper-secures-eu1-6m-in-pre-seed-funding-to-revolutionize-predictive-cybersecurity.md HTML: https://www.whisper.security/resources/blog/whisper-secures-eu1-6m-in-pre-seed-funding-to-revolutionize-predictive-cybersecurity - Meet the Minds Behind the Whisper's Mission: Whisper is a cybersecurity startup using deep internet and mathematical expertise to deliver predictive threat intelligence and prevent cyber threats. Markdown: https://www.whisper.security/resources/blog/meet-the-minds-behind-the-whispers-mission.md HTML: https://www.whisper.security/resources/blog/meet-the-minds-behind-the-whispers-mission - Whisper at Slush'D Heilbronn: A Day of Innovation, Community, and European Optimism: Whisper attended Slush'D Heilbronn, connecting with investors and founders, and celebrating Europe's vibrant, optimistic startup ecosystem. Markdown: https://www.whisper.security/resources/blog/whisper-at-slushd-heilbronn-a-day-of-innovation-community-and-european-optimism.md HTML: https://www.whisper.security/resources/blog/whisper-at-slushd-heilbronn-a-day-of-innovation-community-and-european-optimism - Whisper Emerges from Stealth with 'God Mode' to Predict and Combat Cybercrime: Whisper is a Dutch cybersecurity startup offering a predictive threat intelligence platform that helps organizations detect and stop cyber threats early. Markdown: https://www.whisper.security/resources/blog/whisper-emerges-from-stealth-with-god-mode-to-predict-and-combat-cybercrime.md HTML: https://www.whisper.security/resources/blog/whisper-emerges-from-stealth-with-god-mode-to-predict-and-combat-cybercrime ## Getting Started - Sign up at: https://console.whisper.security/sign-up - Book a demo: https://meetings-eu1.hubspot.com/rsaggar - Product overview: https://www.whisper.security/product - Read the blog: https://www.whisper.security/resources/blog - Documentation: https://www.whisper.security/docs - Splunk Integration: https://www.whisper.security/docs/integrations/splunk/overview - MCP Setup: https://www.whisper.security/docs/ai/mcp/setup - MCP Reference (tools, resources, prompts): https://www.whisper.security/docs/ai/mcp/reference - Cypher Query Guide: https://www.whisper.security/docs/cypher-query-guide - Cypher API Reference: https://www.whisper.security/docs/cypher-api-reference ## Pages Index Marketing & product: - Home: https://www.whisper.security - Product overview: https://www.whisper.security/product - AI Context (MCP): https://www.whisper.security/product/ai-context - Direct API: https://www.whisper.security/product/api-access - Integrations: https://www.whisper.security/product/integrations - Console: https://www.whisper.security/console - Pricing: https://www.whisper.security/pricing - Technology: https://www.whisper.security/technology Company: - About Us: https://www.whisper.security/about-us - Contact: https://www.whisper.security/contact-us Resources: - Documentation: https://www.whisper.security/docs - Glossary: https://www.whisper.security/glossary - FAQ: https://www.whisper.security/faq - Blog: https://www.whisper.security/resources/blog - Resources hub: https://www.whisper.security/resources - White Papers: https://www.whisper.security/resources/white-papers Legal: - Privacy Policy: https://www.whisper.security/privacy-policy - Terms: https://www.whisper.security/terms-conditions ## Social Media - LinkedIn: https://www.linkedin.com/company/whispersecurity - X/Twitter: https://x.com/WhisperSec36054 - YouTube: https://www.youtube.com/@Whisper-Security ## Contact - General: support@whisper.security - Sales/Demo: https://meetings-eu1.hubspot.com/rsaggar - Integration Requests: support@whisper.security - Contact Form: https://www.whisper.security/contact-us