Overview for SOC Teams
- The Problem with SPL for Detection Logic
- What Changes
- The Four Places It Plugs in
- A Worked Example
- Getting the Data Right First
- Where to Next
If you build detections, hunt, or respond to alerts in Splunk, this is the chapter that matters.
The Problem with SPL for Detection Logic
SPL is excellent at finding and aggregating events. It is poor at expressing judgement, and detection engineering is mostly judgement.
Consider a rule you have probably written: "alert when a host talks to a domain that looks algorithmically generated, but only if the volume is unusual for that host, and not if the domain is on our allow-list, and treat first-party CDNs differently".
In SPL that becomes a chain of eval expressions, three lookups, a subsearch
you are slightly afraid of, and a comment explaining what the regular
expression on line 14 is for. It works. Nobody wants to change it.
In Python it is thirty readable lines with a name for each concept.
What Changes
Logic lives where you can read it. Loops, dictionaries, functions,
try/except. A detection becomes something a new starter can follow.
Enrichment happens inline. Threat intelligence, asset ownership, identity context: fetch it in the same search that found the event, rather than in a lookup that is refreshed by a different job you also have to maintain.
Iteration is instant. Change five characters, press enter. No app to package, no restart, no change request.
The prototype is the production artefact. The snippet you refined interactively is the same code the scheduled alert runs and the alert action executes. There is no rewrite step where the logic drifts.
The Four Places It Plugs in
| Where | Command | Runs |
|---|---|---|
| Detections | exec | In a scheduled search, shaping and scoring what it finds |
| Threat hunting | exec | Interactively, while you think |
| Adaptive response | exec in a saved search | From an Enterprise Security notable |
| Alert actions | execalert | After a scheduled search fires, over its results |
And one that does not fit a table: Response plans, where the same code drives multi-step containment.
A Worked Example
Data exfiltration to a newly-registered domain. The detection has to combine volume, domain shape and history, which is three different kinds of judgement.
index=proxy earliest=-1h
| stats sum(bytes_out) as bytes_out count as requests
dc(http_user_agent) as agents by host, user, dest_host
| exec inline="import math
def entropy(text):
counts = {}
for ch in text:
counts[ch] = counts.get(ch, 0) + 1
total = len(text)
return -sum((c/total) * math.log2(c/total) for c in counts.values())
ALLOWED_SUFFIXES = ('.microsoft.com', '.github.com', '.splunk.com', '.gov.uk')
findings = []
for e in events:
domain = e['dest_host']
if domain.endswith(ALLOWED_SUFFIXES):
continue
labels = domain.split('.')
score = 0
reasons = []
h = entropy(domain)
if h > 3.6:
score += 30
reasons.append(f'high entropy ({h:.2f})')
longest = max(len(l) for l in labels)
if longest > 15:
score += 25
reasons.append(f'long label ({longest} chars)')
megabytes = int(e['bytes_out']) / 1048576
if megabytes > 50:
score += 30
reasons.append(f'{megabytes:.0f} MB outbound')
if int(e['agents']) == 1 and int(e['requests']) > 50:
score += 15
reasons.append('single user agent, high volume')
if score >= 50:
e['risk_score'] = score
e['reasons'] = reasons
e['megabytes_out'] = round(megabytes, 1)
e['entropy'] = round(h, 2)
findings.append(e)
events = findings"
| sort - risk_score
| table host user dest_host risk_score megabytes_out entropy reasons

Every threshold has a name. Every contribution to the score is attached to the result as a reason, so the analyst who picks up the alert can see why it fired without opening the detection. That last property is worth more than the detection itself.
Getting the Data Right First
Do the heavy lifting in SPL and hand a small, shaped result to Python.
index=proxy
| stats sum(bytes_out) as bytes_out by host, dest_host ← SPL: aggregate
| exec inline="..." ← Python: judge
Aggregating 4 million events into 300 rows and then scoring them is fast. Scoring all 4 million works too, but there is rarely a reason to.
Where to Next
- Detections for scheduled rules and scoring.
- Threat hunting for the interactive workflow.
- Replacing SOAR workflows if you are carrying a SOAR platform for a handful of playbooks.
- AI agents if you are pointing a model at your data.