Skip to contentSkip navigation

Enterprise Security Integration

Splunk ES compatibility: the threat-intel collections and what currently prevents them populating, adaptive response, CIM aliases, graph macros, and worked detections corrected against live field names.

Splunk
On this page (14)

Enterprise Security Integration Documentation

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

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

Threat Intelligence framework integration

Status: Known issue — cannot populate (F-SP-2). Both shipped populator searches are unfiltered label scans over the whole graph, and neither returns rows against production — measured, including with the shipped LIMIT lowered to five. Neither collection fills today, so the ES Threat Intelligence integration described in this section does not currently work end to end. The section stays because being told which step fails is better than following four steps into silence.

The ES Threat Intelligence framework consumes KV Store collections that match the ES ip_intel and domain_intel schemas. The add-on ships two:

CollectionMatches ES schemaPopulated byStatus
whisper_ip_intelip_intelWhisper - Populate IP Threat Intel KV Store (disabled), or the whisper_threat_intel modular inputKnown issue — cannot populate (F-SP-2)
whisper_domain_inteldomain_intelWhisper - Populate Domain Threat Intel KV Store (disabled), or the whisper_threat_intel modular inputKnown issue — cannot populate (F-SP-2)

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, outputlookups to the collection, and tags each record with threat_category = whisper so ES can surface them as a feed source.

On the modular-input path, 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.

That path writes through KV Store batch_save (up to 1000 records per batch) for efficient bulk population. If a batch fails, it falls back to per-record inserts to maximise coverage.

Collection schemas

There are two writers and one declaration, and all three disagree. The tables below name the writer for every field, so a detection is built against what arrives rather than against what collections.conf says. Two of the columns the populator searches write -- threat_category and threat_score -- are not declared in collections.conf at all, and four of the columns it does declare are written by nothing.

whisper_ip_intel (ES ip_intel-compatible):

FieldTypeWritten byDescription
ipstringbothIP address (lookup key)
descriptionstringbothThreat description
threat_groupstringbothwhisper_graph
threat_categorystringpopulator searchwhisper — the value ES surfaces as the feed source
threat_scorenumberpopulator searchRaw threat score from the graph
threat_keystringmodular inputThe indicator's feed sources, formatted
threat_collection_namestringmodular inputES collection name this record belongs to
threat_collection_keystringmodular inputES collection key (identifier within the collection)
weightnumbermodular inputES threat weight: 1 (low), 2 (medium), 3 (high)
whisper_threat_scorenumbermodular inputRaw threat score from CALL explain()
whisper_threat_levelstringmodular inputThreat level from CALL explain()
whisper_risk_scorenumbermodular inputNormalised risk score (0-100)
whisper_risk_levelstringmodular inputinformational / low / medium / high / critical
whisper_asn, whisper_asn_name, whisper_country, whisper_prefixstringnothingDeclared in collections.conf and never written. Do not key a detection on them

whisper_domain_intel (ES domain_intel-compatible) carries the same fields with domain as the lookup key in place of ip. It declares two infrastructure fields rather than four — whisper_asn_name and whisper_country — and those two are likewise written by nothing.

The two writers differ on one field that matters: the populator searches tag records threat_category = whisper, the modular input writes threat_key. A detection that filters on one will not see records written by the other.

Enabling the populators

Status: Known issue — cannot populate (F-SP-2). Step 4 does not complete today: both searches are refused before they return rows, so the feeds never appear in ES. The procedure is left here so you can recognise where it stops rather than assume you configured it wrong.

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. They will not, in this release — see the status above.

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, 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:

TemplateWhat it does
Example - Whisper - Enrich DNS DomainsEnrich DNS query values with whisperlookup, write to <destination_index> as sourcetype=whisper:enriched_dns
Example - Whisper - Enrich Destination IPsEnrich dest_ip from network traffic, write as sourcetype=whisper:enriched_ip
Example - Whisper - Enrich Proxy HostnamesEnrich url_domain from proxy logs, write as sourcetype=whisper:enriched_proxy
Example - Whisper - Custom Graph Query EnrichmentRun an arbitrary Cypher query per indicator via whisperquery, write as sourcetype=whisper:enriched_custom

Each template's search field uses <source_index>, <source_sourcetype>, <destination_index>, and <indicator_field> 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:

MacroArgumentsPurpose
whisper_index--Index override used by every shipped dashboard/search (default index=whisper)
whisper_shared_nameservers(domain)domainDomains sharing nameservers with the given domain
whisper_asn_infrastructure(asn)asnPrefixes routed by an ASN
whisper_cname_chain(domain)domainCNAME chain resolution up to 5 hops
whisper_spf_chain(domain)domainSPF include chain up to 3 hops
whisper_bgp_peers(asn)asnBGP peers of an ASN with name + country
whisper_cohosted_domains(domain)domainDomains co-hosted on the same IP
whisper_full_investigation(indicator)indicatorhostname -> IP -> ASN with geo and reverse DNS
whisper_explain(indicator)indicatorThreat 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=<dns_index> sourcetype=<dns_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=<firewall_index> sourcetype=<firewall_sourcetype> earliest=-15m action=allowed
| rename dest_ip AS indicator
| dedup src_ip indicator
| `whisper_explain(indicator)`
| spath sources{}.feedId 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

CALL explain() returns its feed evidence in a column called sources, an array of objects each carrying feedId, weight, firstSeen and lastSeen. Extract sources{}.feedId rather than sources{} so mvcount counts feeds and not objects. Verified against production on 2026-08-09.

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=<firewall_index> sourcetype=<firewall_sourcetype> earliest=-30m action=allowed
| rename dest_ip AS indicator
| dedup src_ip indicator
| whisperlookup field=indicator type=ip
| where whisper_cohost_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_cohost_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:

FieldDescription
whisper_risk_scoreNormalised 0-100, aliased to CIM risk_score
whisper_risk_levelinformational / low / medium / high / critical
whisper_risk_factors_listComma-separated list of contributing factors
whisper_risk_componentsJSON 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 for the full factor list and weights.

Example RBA trigger based on inline risk scores:

spl
index=<source_index> sourcetype=<source_sourcetype> earliest=-15m
| rename <indicator_field> 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.
  • 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.
  • Saved Searches -- full list of shipping utility and example searches.
  • Macros -- every whisper_* graph macro with parameters and example output.
  • CIM Mapping -- full CIM alias table and data-model coverage.
  • Enrichment -- risk factor list and scoring engine.
  • Search Commands -- whisperlookup, whisperquery, and the rest of the command set.