Reference
Every sourcetype, field alias, lookup, collection, macro and saved search the add-on ships, with a status on each row.
On this page (25)
- Source types
- whisper:attack_surface
- whisper:attack_surface_change
- whisper:spf_compliance
- whisper:threat_intel
- whisper:watchlist
- whisper:enrichment
- ta_whisper_graph
- Index
- CIM mapping
- Field aliases
- Validating CIM compliance
- ES threat-intel collections
- Lookups
- Risk factors and risk levels
- KV Store collections
- Macros
- Traversal ceilings, stated
- The two macros that need an API key
- The whisper_index macro
- Combining macros with SPL
- Saved searches
- Customising an enrichment template
- Building your own detections
- Where this page changes
Reference Documentation
The named-object census for the Whisper Security Add-on for Splunk (TA-whisper-graph): every sourcetype, field alias, lookup, collection, macro and saved search the package declares, each listed once, each with a Status saying whether it does anything on a default install.
This page is deliberately the opposite of the Splunk overview. The overview is short and sells. This is complete, and it says out loud where the package disagrees with itself. It reads worse and it stays true longer.
Where these rows come from. Every one was read out of the shipped TA-whisper-graph-1.0.0 package on 2026-08-10 — default/*.conf, lookups/*.csv and bin/*.py, unpacked from the tarball published on Splunkbase. Where the package and an earlier version of this documentation disagreed, the package won and the row says so.
Two classes of object are documented where you use them rather than here, because you operate them instead of looking them up: the search commands, on Using It, and the modular inputs, on the same page.
| Status | Means |
|---|---|
| Live | The package writes it on a default install, once the input or command that owns it runs. |
| Conditional | Ships and works, but only after you do the thing named in the row. |
| Declared, never written | The object exists in a .conf file and nothing in the package writes it. Do not key anything on it. |
| Known issue | Ships and cannot do its job in this release, and the row says why. Disclosed rather than concealed, because following a procedure into silence is worse than being told where it stops. |
Source types
Six sourcetypes carry data and one carries the add-on's own log. Every data sourcetype is written by a modular input, and the index is that input's index setting, which defaults to whisper on all three.
| Sourcetype | Written by | CIM | Status |
|---|---|---|---|
whisper:attack_surface | Attack Surface Baseline input | not normalised | Live |
whisper:attack_surface_change | the same input's change detector, from its second run onward | not normalised | Known issue — extraction configured under the wrong name |
whisper:spf_compliance | Attack Surface Baseline input | not normalised | Live |
whisper:threat_intel | ES Threat Intel Feed input | Threat Intelligence | Live |
whisper:watchlist | Watchlist Enrichment input | Threat Intelligence | Conditional — needs a watchlist CSV the package does not ship |
whisper:enrichment | Watchlist Enrichment input | Network Resolution, DNS | Conditional — same CSV |
ta_whisper_graph | the add-on's own log file, routed by props.conf | -- | Live |
whisper:attack_surface
One event per DNS record observed for a monitored domain: domain, record_type (A, NS, MX, CNAME, subdomain), record_value, collected_at, collection_id. The collection_id is a short identifier shared by every event in one run of the input — it is how you scope a query to a single snapshot rather than to a time window.
whisper:attack_surface_change
Emitted by the change detector inside the baseline input, comparing the current snapshot against the previous one. Fields: domain, record_type, change_type (added or removed), old_value, new_value, detected_at, risk_score. The first run of the input only writes a baseline; changes start from the second run.
Status: known issue — extraction is configured under the wrong name.
props.confdeclares[whisper:change]withKV_MODE = json— a sourcetype nothing emits — and carries no stanza forwhisper:attack_surface_change. The JSON extraction that the Attack Surface Change Timeline dashboard reads is therefore attached to a name no event ever has. Engineering owns adding the stanza.
Nameserver and mail-record changes, and additions of a wildcard record, also produce a second event with sourcetype=stash in the ES risk format: risk_score, risk_object, risk_object_type, risk_message, threat_object, source, search_name, and a mitre_attack entry for T1584 (Compromise Infrastructure).
whisper:spf_compliance
Written by the baseline input alongside the DNS snapshot, one event per monitored domain: domain, last_checked, spf_exists, include_count, authorized_ip_count, authorized_ips, spf_chain, exceeds_limit.
Two things about this sourcetype are worth knowing before you build on it. props.conf declares FIELDALIAS-last_checked = collected_at AS last_checked on it, but the event is written with last_checked already populated and carries no collected_at at all, so the alias is a no-op — harmless, and not the source of the field. And exceeds_limit is derived from the include count alone, while RFC 7208 counts includes, redirects, exists, a and mx mechanisms together: a domain can read false here and still breach the RFC.
whisper:threat_intel
One event per indicator whose explain score is above zero, plus one summary event per run. Each event is an ES intel record with a record_type of ip_intel or domain_intel: ip or domain, description, threat_key, threat_group, weight, threat_collection_name, threat_collection_key, whisper_threat_score, whisper_threat_level, whisper_risk_score, whisper_risk_level, _time. Turning on Include Infrastructure Enrichment on the input — off by default — adds whisper_asn, whisper_asn_name, whisper_country and whisper_prefix to IP records.
Corrected. Earlier documentation gave this sourcetype's index as _internal and its fields as indicator / indicator_type. inputs.conf sets index = whisper, and the records are keyed on ip or domain.
whisper:watchlist
Written by the Watchlist Enrichment input, one event per enriched indicator: _key, indicator, indicator_type, enrichment_data (the whole enrichment result as a JSON string), enriched_at, and _raw_enrichment — the same result again as a nested object, which is serialised into this event before it is stripped for the flat copy described below.
Status: conditional. The input loads its indicators from
lookups/whisper_watchlist.csv, and the package does not ship that file. Until you create it, the input logs that it found no indicators and writes nothing. The CSV needs anindicatorcolumn; an optionalindicator_typecolumn ofipordomainoverrides auto-detection.
whisper:enrichment
Every CIM claim the add-on makes rests on this sourcetype, so it is worth being exact about who writes it. The Watchlist Enrichment input emits one flat, whisper_-prefixed copy of each enrichment result under whisper:enrichment, immediately after the whisper:watchlist record. Nothing else in the package writes it.
In particular, whisperlookup does not. It is a streaming command: it enriches rows in flight and indexes nothing, so events that pass through it keep the sourcetype of the search that produced them. Earlier documentation credited this sourcetype to whisperlookup, which is why the CIM verification searches further down this page were published as though any install would return rows for them.
That has one consequence worth acting on. The shipped enrichment templates | collect into whisper:enriched_dns, whisper:enriched_ip, whisper:enriched_proxy and whisper:enriched_custom, and none of those four has a props.conf stanza. If you want the CIM aliases on collected enrichment, set the template's destination sourcetype to whisper:enrichment.
ta_whisper_graph
The add-on's own operational log, in _internal. props.conf routes source::...ta_whisper_graph.log to this sourcetype. It is what Troubleshooting reads.
Index
All three inputs default to index = whisper and expose Index as a per-input setting. The dashboards and the shipped searches reference the whisper_index macro instead of a literal index name, so if you write Whisper events somewhere else, override the macro rather than editing dashboards. authorize.conf grants the whisper_user role search access to whisper and _internal, defaulting to whisper.
CIM mapping
props.conf carries the field aliases and tags.conf the tag assignments, both keyed on the event types in eventtypes.conf. Together they make Whisper events readable by CIM-based dashboards, reports and Enterprise Security.
| Event type | Sourcetype | Tags | CIM data models | Status |
|---|---|---|---|---|
whisper_enrichment | whisper:enrichment | network, resolution, dns | Network Resolution, DNS | Conditional — the sourcetype exists only where the watchlist input has run |
whisper_threat_intel | whisper:threat_intel | threat, report | Threat Intelligence | Live |
whisper_watchlist | whisper:watchlist | threat, report | Threat Intelligence | Conditional |
| -- (none) | whisper:attack_surface, whisper:attack_surface_change, whisper:spf_compliance | -- | none claimed | Live |
The attack-surface sourcetypes are not CIM-normalised, and that is deliberate. They describe DNS posture for a monitored domain —
domain,record_type,record_value,collection_id— not network connections. There is nosrc_ip/dest_ip/transport/bytespattern to map, so the add-on claims no Network Traffic compliance for them. Query them by their native field names.
Field aliases
props.conf declares twenty FIELDALIAS entries under [whisper:enrichment]. Three of the CIM names in that list are also written directly into whisperlookup's output rows, from CIM_FIELD_MAP in whisper_field_mapper.py. The other seventeen are search-time aliases and fire only on indexed events whose sourcetype is whisper:enrichment — which is why | whisperlookup ... | where threat_score > 50 matches nothing, and whisper_threat_score is the field to use there instead.
| Whisper field | CIM field | On whisperlookup output |
|---|---|---|
whisper_ip | dest_ip | yes |
whisper_country | dest_country | yes |
whisper_asn | dest_asn | yes |
whisper_threat_score | threat_score | alias only |
whisper_threat_level | threat_level | alias only |
whisper_is_threat | is_threat | alias only |
whisper_is_c2 | is_c2 | alias only |
whisper_is_tor | is_tor | alias only |
whisper_is_malware | is_malware | alias only |
whisper_is_phishing | is_phishing | alias only |
whisper_is_anonymizer | is_anonymizer | alias only |
whisper_is_spam | is_spam | alias only |
whisper_is_bruteforce | is_bruteforce | alias only |
whisper_is_scanner | is_scanner | alias only |
whisper_is_blacklist | is_blacklist | alias only |
whisper_is_proxy | is_proxy | alias only |
whisper_is_vpn | is_vpn | alias only |
whisper_is_whitelist | is_whitelist | alias only |
whisper_risk_score | risk_score | alias only |
whisper_risk_level | risk_level | alias only |
One further alias ships outside this stanza, on whisper:spf_compliance, and it is a no-op — see whisper:spf_compliance above.
[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"
The two EVAL lines are the only computed fields: vendor is set to Whisper Security and vendor_product to Whisper Knowledge Graph on every whisper:enrichment event, for CIM vendor identification.
Both names exist on the event. The alias adds the CIM field; it does not rename the original. Use the
whisper_prefixed field for Whisper-specific searches and the CIM field for cross-vendor dashboards and data-model searches.
Validating CIM compliance
Both of these return rows once the watchlist input has run at least once, and no rows before that.
tag=network tag=resolution tag=dns
| head 10
| table _time dest_ip dest_country dest_asn vendor vendor_product
sourcetype=whisper:enrichment
| head 10
| table whisper_ip dest_ip whisper_country dest_country whisper_asn dest_asn
ES threat-intel collections
whisper_ip_intel and whisper_domain_intel match the ES ip_intel and domain_intel schemas. They have two writers that disagree with each other and with collections.conf, and neither of them can currently fill the collection. The field-by-field schema, naming the writer of each column, is on Enterprise Security — build detections against that table, not against collections.conf.
Lookups
The package ships five CSV lookup files under lookups/, each with a matching definition in transforms.conf.
Read the shipped rows with | inputlookup, not from this page. An earlier version of this documentation transcribed the ASN lists, and five of the eight bulletproof-hosting rows were wrong — including one that named DigitalOcean as bulletproof hosting on a page that listed the same ASN as a CDN two sections further down. A reader who copied that into a detection scored every DigitalOcean-hosted asset as hostile. The transcription is gone; the lookup is the source.
| inputlookup whisper_high_risk_asns
| Lookup | Columns | Match | Status |
|---|---|---|---|
whisper_high_risk_asns | asn, description, category | exact, first match | Live — category ships as bulletproof, not bulletproof_hosting |
whisper_dns_providers | nameserver_pattern, provider | WILDCARD(nameserver_pattern) | Live |
whisper_cdn_asns | asn, provider | exact, first match | Live |
whisper_org_asns | asn, description | exact | Ships empty — header row only |
whisper_risk_factors | factor, points, description | exact | Live — read by the risk scorer on every enrichment |
What each is for, since the file names do not say it:
whisper_high_risk_asns— Autonomous Systems known for hosting malicious infrastructure. Tag or score traffic to IPs on them in searches you write.whisper_dns_providers— major providers whose nameservers are shared by design. A shared nameserver on one of these is expected, so exclude them from any shared-nameserver search or it will return the internet.whisper_cdn_asns— CDN and large SaaS ASNs. IPs on them host enormous numbers of names, so co-hosting density carries no signal there.whisper_org_asns— your own ASNs. Nothing reads it until you populate it; once you do, a search of your own can watch for another AS announcing your prefixes.whisper_risk_factors— the weight of each signal the risk scorer adds up. See below.
To edit any of them, go to Settings > Lookups > Lookup table files, find the lookup and click Edit; changes take effect on the next search that reads it. To keep your edits out of the way of an upgrade, put a copy in local/ instead of editing the shipped file.
Risk factors and risk levels
whisper_risk_factors.csv sets the points each signal contributes. Read it with | inputlookup whisper_risk_factors; a row you add with an existing factor name overrides the built-in default for that factor, and a negative points value reduces the score rather than raising it.
Raw points are summed and normalised onto a whisper_risk_score, which _score_to_level() bands:
| Score | whisper_risk_level |
|---|---|
| 80–100 | critical |
| 60–79 | high |
| 40–59 | medium |
| 20–39 | low |
| 0–19 | informational |
Corrected. Earlier documentation gave four bands, each shifted one level up, and omitted informational entirely: a score of 45 was published as HIGH where the code returns medium, and 10 as LOW where it returns informational.
whisper_risk_level is the add-on's own arithmetic and is a different field from whisper_threat_level, which comes back from the graph. When the graph does not return a level, the add-on derives one from the score, and that ladder has six values, not five: NONE, INFO, LOW, MEDIUM, HIGH, CRITICAL.
Read
coveragebeforeband. 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.explaindoes not returncoverageat all. Full contract: Coverage — what we looked at.
KV Store collections
collections.conf declares six collections. They are managed by the inputs, the commands and the populator saved searches; editing them by hand is not supported.
| Collection | Written by | Read by | Status |
|---|---|---|---|
whisper_enrichment_cache | whisperlookup | whisperlookup | Live |
whisper_precomputed_enrichment | Whisper - Populate Precomputed Enrichment KV Store | the whisper_domain_lookup and whisper_ip_lookup definitions | Conditional — that saved search ships disabled |
whisper_ip_intel | populator saved search, or the threat-intel input | Splunk ES | Known issue — cannot populate |
whisper_domain_intel | populator saved search, or the threat-intel input | Splunk ES | Known issue — cannot populate |
whisper_watchlist | nothing | nothing | Declared, never written — the shipped input reads a CSV instead |
whisper_dns_baseline | nothing | nothing | Declared, never written — the baseline input writes events |
Corrected. Earlier documentation listed five collections and credited whisper_dns_baseline to the baseline input. Six are declared, and two of the six have no writer in this release. The helper that would seed whisper_watchlist from whisper_dns_baseline is in the package's library and is called by nothing.
The enrichment cache is cleared with whisperflush:
| whisperflush collection=cache # Clear enrichment cache
| whisperflush collection=precomputed # Clear precomputed data
| whisperflush collection=all # Clear everything
Expired entries are removed by the Whisper - Evict Expired Cache Entries saved search, which ships disabled — see Saved searches.
Macros
macros.conf ships nine stanzas: eight investigation macros, each wrapping whisperquery around one parameterised Cypher query, and the index macro. Call one with backtick syntax.
| `whisper_shared_nameservers("phishing-target.com")`
| Macro | Argument | Output fields | What it answers | Status |
|---|---|---|---|---|
whisper_shared_nameservers | domain | nameserver, related_domain | Which other domains sit on this domain's nameservers — common ownership, or common compromised hosting | Live |
whisper_asn_infrastructure | ASN | asn, prefix | Every prefix an AS routes. Scopes a bulletproof provider or a threat actor's network | Live |
whisper_cname_chain | domain | cname_chain, cname_target, depth | Where an alias actually lands — dangling CNAMEs, and therefore subdomain-takeover exposure | Live — needs an API key |
whisper_spf_chain | domain | spf_chain, depth | Which third parties a domain authorises to send its mail, and how far the include chain reaches | Live — needs an API key |
whisper_bgp_peers | ASN | peer_asn, peer_name, country | A network's transit relationships, and unusual peering | Live |
whisper_cohosted_domains | domain | ip, cohosted_domain | What else is on the same IP. Low density means a dedicated host, which is itself a signal | Live |
whisper_full_investigation | indicator | hostname, ip, prefix, asn, asn_name, country, cohost_count | Resolution, routing, geography and co-hosting in one command, for triage | Live |
whisper_explain | indicator | the explain() result: score, level, explanation, contributing factors | A threat verdict on one indicator without running the enrichment pipeline | Live |
Traversal ceilings, stated
whisper_cname_chain follows ALIAS_OF up to five hops and whisper_spf_chain follows SPF_INCLUDE up to three. Both ceilings are in the macro definitions, and a chain longer than the ceiling comes back truncated rather than flagged.
The SPF macro performs no compliance check of any kind. It returns the chain and its length, and it cannot observe an RFC 7208 lookup violation — a three-hop traversal is not a lookup count. Earlier documentation claimed it checked RFC 7208 compliance; it never did.
The two macros that need an API key
whisper_cname_chain and whisper_spf_chain are refused without an account. Every other macro works without one, as do whisperlookup and whisperquery. See Do you need an API key? for where the key is stored.
The whisper_index macro
| Macro | Default | Purpose |
|---|---|---|
whisper_index | index=whisper | The index every shipped dashboard and search reads. Override it in Settings > Advanced Search > Search Macros, or in local/macros.conf, if you write Whisper events to a different index. |
Combining macros with SPL
Macros return tabular results, so they pipe into anything.
Filter shared-nameserver results down to the ones already known bad:
| `whisper_shared_nameservers("target.com")`
| lookup whisper_domain_intel domain AS related_domain
| where isnotnull(threat_key)
Enumerate the networks behind suspicious enriched events:
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 a CNAME chain to a lookup for later comparison:
| `whisper_cname_chain("example.com")`
| outputlookup whisper_cname_results.csv
Saved searches
savedsearches.conf ships eight stanzas and all eight are disabled. Four are utilities; four are example enrichment templates you clone.
Two of the three workflows people expect to find here are not saved searches at all. Ad-hoc indicator investigation runs on demand from the Lookup / Investigation dashboard, and owned-domain monitoring is driven by the Attack Surface Baseline modular input and read on the Attack Surface Change Timeline dashboard.
| Stanza | What it does | Status |
|---|---|---|
Whisper - Evict Expired Cache Entries | Runs whisperevict hourly against whisper_enrichment_cache | Ships disabled — enable if you use enrichment caching |
Whisper - Populate IP Threat Intel KV Store | Fills whisper_ip_intel for the ES Threat Intelligence framework | Known issue — cannot populate |
Whisper - Populate Domain Threat Intel KV Store | Fills whisper_domain_intel | Known issue — cannot populate |
Whisper - Populate Precomputed Enrichment KV Store | Pre-warms the enrichment cache for indicators you use often | Ships disabled |
Example - Whisper - Enrich DNS Domains | Enrich DNS query domains into a destination index | Template — carries placeholders, not runnable as shipped |
Example - Whisper - Enrich Destination IPs | Enrich destination IPs from network traffic | Template — carries placeholders |
Example - Whisper - Enrich Proxy Hostnames | Enrich proxy and web hostnames | Template — carries placeholders |
Example - Whisper - Custom Graph Query Enrichment | Run your own Cypher and collect the result | Template — carries placeholders |
The two populators are keyed differently from the modular input that writes the same collections, and neither returns rows today. Enterprise Security documents which step fails and what the collections look like when they do fill.
The add-on ships no correlation searches. Eight stanzas is the whole of savedsearches.conf, they are all above, and none of them is a detection.
Customising an enrichment template
Each template is disabled and carries placeholders. To adopt one:
- Copy the stanza into
local/savedsearches.confand rename it, for exampleMy Company - Enrich DNS Domains. - Replace the placeholders:
<source_index>,<source_sourcetype>, the<indicator_field>holding the IOC, and<destination_index>. - Set the destination sourcetype. The shipped stanzas write
whisper:enriched_dnsand its siblings, which have noprops.confstanza — writewhisper:enrichmentinstead if you want the CIM aliases. - Adjust
dispatch.earliest_timeandcron_scheduleto match your dedup window. - Set
disabled = 0andenableSched = 1.
The body of a template, with the placeholders in place:
index=<source_index> earliest=-15m <indicator_field>=*
| rename <indicator_field> as indicator
| dedup indicator
| whisperlookup field=indicator type=<domain|ip|auto>
| collect index=<destination_index> sourcetype="<destination_sourcetype>"
Building your own detections
There is no correlation-search pack to enable, so a detection is something you write. Either clone the enrichment template closest to your data and point it at the index you want watched, or write SPL directly against whisperquery and the whisper_ fields whisperlookup produces — the macros above are the shortest path to the second.
Where this page changes
Every row above is a stanza in a .conf file or a line in a .csv, which is what makes the page checkable rather than merely careful: unpack the release, enumerate the stanzas, diff. Releases, and which documented behaviour each one changed, are on the Release History page.