Skip to main content
DFIRLab
Research
Intel BriefingsThreat Actors
IOC CheckFile AnalyzerPhishing CheckDomain LookupExposure ScannerPrivacy Check
WikiAbout
PlatformNew
DFIRLab

Security research, threat intelligence, and free DFIR tools.

Tools

Phishing CheckerExposure ScannerDomain LookupFile AnalyzerPrivacy Check

Resources

DFIR WikiIntel BriefingsAboutPlatformAPI Docs

Legal

Privacy PolicyRSS FeedSitemap

© 2026 DFIR Lab. All rights reserved.


← Back to Research
SplunkIOC EnrichmentCustom Search CommandSIEMThreat Intelligencesoc

DFIR Platform + Splunk: IOC Enrichment via Custom Search Commands

DFIR Lab/April 14, 2026/11 min read

Splunk is where most SOC teams live. Alerts fire, dashboards update, and investigations unfold inside SPL queries. But Splunk's native capabilities stop at the data you ingest — it does not natively reach out to external threat intelligence sources to tell you whether an IP in your logs is a known botnet controller or a benign CDN node. That context gap is why a Splunk IOC enrichment app that integrates external intelligence directly into the search pipeline is one of the highest-value additions to any Splunk deployment.

This guide walks through building a custom search command that calls DFIR Platform's multi-source IOC enrichment API from within SPL. The result: analysts can enrich any observable — IPs, domains, hashes, URLs — without leaving Splunk, and enrichment data flows directly into existing dashboards, alerts, and reports.


How Splunk Custom Search Commands Work

Splunk's extensibility model includes custom search commands — Python scripts that execute as part of the SPL pipeline. When you run a query like:

spl
1index=firewall src_ip=* | dfir_enrich type=ip field=src_ip

Splunk pipes each event through the dfir_enrich command, which can read event fields, call external APIs, and append new fields to the output. The enriched events continue through the pipeline like any other Splunk data — available for filtering, aggregation, visualization, and alerting.

Custom search commands come in several types:

  • Streaming commands process events one at a time, adding or modifying fields. Best for per-event enrichment.
  • Generating commands produce events from scratch (like | makeresults). Useful for standalone IOC lookups.
  • Transforming commands aggregate events. Less relevant for enrichment use cases.

For IOC enrichment, a streaming command is the natural fit. It receives events from the upstream pipeline, extracts the IOC value from a specified field, calls the DFIR Platform API, and appends enrichment fields (risk score, verdict, source count) to each event.


Prerequisites

  • Splunk Enterprise 8.x or 9.x, or Splunk Cloud with private app installation enabled
  • Python 3 — Splunk 8.x+ ships with Python 3; ensure your instance uses it
  • Splunk SDK for Python (splunklib) — included with the Splunk Python SDK
  • DFIR Platform API key — sign up at platform.dfir-lab.ch (free tier: 100 credits/month, no credit card required)
  • Network connectivity from the Splunk search head to api.dfir-lab.ch on port 443

Getting Started

Create your free account at platform.dfir-lab.ch and generate an API key from the dashboard. You will configure this key in the Splunk app's configuration file. The free tier provides 100 credits per month for evaluation — approximately 20-33 IOC lookups at 3-5 credits each.


Building the Splunk IOC Enrichment App

The app follows Splunk's standard directory structure. Below is the complete layout with all files needed.

App Directory Structure

dfir_platform/
  default/
    app.conf
    commands.conf
    dfir_platform.conf
  bin/
    dfir_enrich.py
  metadata/
    default.meta
  README

App Configuration (default/app.conf)

ini
1[install]
2is_configured = false
3build = 1
4 
5[ui]
6is_visible = true
7label = DFIR Platform IOC Enrichment
8 
9[launcher]
10author = DFIR Lab
11description = Multi-source IOC enrichment via DFIR Platform API
12version = 1.0.0

Search Command Registration (default/commands.conf)

ini
1[dfir_enrich]
2filename = dfir_enrich.py
3chunked = true
4python.version = python3

The chunked = true setting uses Splunk's chunked custom search command protocol, which is the recommended approach for Python 3 commands. It handles serialization and event batching automatically.

API Configuration (default/dfir_platform.conf)

ini
1[api]
2api_url = https://api.dfir-lab.ch/v1
3api_key = YOUR_API_KEY_HERE

Metadata (metadata/default.meta)

ini
1[]
2access = read : [ * ], write : [ admin ]
3export = system

The Custom Search Command (bin/dfir_enrich.py)

python
1#!/usr/bin/env python3
2"""
3Splunk custom search command for DFIR Platform IOC enrichment.
4 
5Usage in SPL:
6 | dfir_enrich type=ip field=src_ip
7 | dfir_enrich type=domain field=query_domain
8 | dfir_enrich type=hash field=file_hash
9 | dfir_enrich type=ip value=203.0.113.47
10"""
11 
12import os
13import sys
14import json
15import configparser
16 
17# Add the Splunk SDK to the path
18sys.path.insert(
19 0,
20 os.path.join(os.path.dirname(__file__), "..", "lib"),
21)
22 
23from splunklib.searchcommands import (
24 dispatch,
25 StreamingCommand,
26 Configuration,
27 Option,
28 validators,
29)
30import requests
31 
32 
33def load_api_config():
34 """Load API configuration from dfir_platform.conf."""
35 app_dir = os.path.join(os.path.dirname(__file__), "..")
36 conf_path = os.path.join(app_dir, "default", "dfir_platform.conf")
37 
38 config = configparser.ConfigParser()
39 config.read(conf_path)
40 
41 return {
42 "api_url": config.get("api", "api_url", fallback="https://api.dfir-lab.ch/v1"),
43 "api_key": config.get("api", "api_key", fallback=""),
44 }
45 
46 
47@Configuration()
48class DFIREnrichCommand(StreamingCommand):
49 """Enrich IOCs using DFIR Platform multi-source API."""
50 
51 type = Option(
52 doc="IOC type: ip, domain, hash, or url",
53 require=True,
54 validate=validators.Set("ip", "domain", "hash", "url"),
55 )
56 
57 field = Option(
58 doc="Event field containing the IOC value to enrich",
59 require=False,
60 )
61 
62 value = Option(
63 doc="Static IOC value to enrich (alternative to field)",
64 require=False,
65 )
66 
67 def __init__(self):
68 super().__init__()
69 self._config = load_api_config()
70 self._cache = {}
71 
72 def _enrich(self, ioc_value: str) -> dict:
73 """Call DFIR Platform API for enrichment, with caching."""
74 cache_key = f"{self.type}:{ioc_value}"
75 if cache_key in self._cache:
76 return self._cache[cache_key]
77 
78 headers = {
79 "X-API-Key": self._config['api_key'],
80 "Content-Type": "application/json",
81 }
82 
83 try:
84 response = requests.post(
85 f"{self._config['api_url']}/ioc/enrich",
86 headers=headers,
87 json={"type": self.type, "value": ioc_value},
88 timeout=30,
89 )
90 response.raise_for_status()
91 result = response.json()
92 self._cache[cache_key] = result
93 return result
94 except requests.exceptions.RequestException as e:
95 return {"error": str(e), "risk_score": -1}
96 
97 def stream(self, records):
98 """Process each event, enriching the specified IOC."""
99 for record in records:
100 # Determine the IOC value to enrich
101 ioc_value = None
102 if self.value:
103 ioc_value = self.value
104 elif self.field and self.field in record:
105 ioc_value = record[self.field]
106 
107 if not ioc_value:
108 # No value to enrich — pass through unchanged
109 yield record
110 continue
111 
112 result = self._enrich(str(ioc_value))
113 
114 # Append enrichment fields to the event
115 record["dfir_risk_score"] = result.get("risk_score", -1)
116 record["dfir_verdict"] = result.get("verdict", "unknown")
117 record["dfir_sources_queried"] = result.get("sources_queried", 0)
118 record["dfir_sources_flagged"] = result.get("sources_flagged", 0)
119 record["dfir_tags"] = json.dumps(result.get("tags", []))
120 
121 # Add error field if present
122 if "error" in result:
123 record["dfir_error"] = result["error"]
124 
125 yield record
126 
127 
128dispatch(DFIREnrichCommand, sys.argv, sys.stdin, sys.stdout, __name__)

The command includes an in-memory cache that deduplicates API calls within a single search execution. If the same IP appears in 500 events, the API is called once and the result is reused for all 500 — critical for both performance and credit conservation.


Installing the Splunk IOC Enrichment App

Manual Installation

  1. Copy the dfir_platform/ directory to your Splunk apps directory:
bash
1cp -r dfir_platform/ $SPLUNK_HOME/etc/apps/dfir_platform/
  1. Install the requests library in Splunk's Python environment:
bash
1$SPLUNK_HOME/bin/splunk cmd python3 -m pip install requests \
2 --target=$SPLUNK_HOME/etc/apps/dfir_platform/lib/
  1. Edit $SPLUNK_HOME/etc/apps/dfir_platform/default/dfir_platform.conf and replace YOUR_API_KEY_HERE with your actual API key from platform.dfir-lab.ch.

  2. Restart Splunk:

bash
1$SPLUNK_HOME/bin/splunk restart

Packaging as a .spl File

To distribute the app across multiple Splunk instances or upload to Splunk Cloud:

bash
1cd $SPLUNK_HOME/etc/apps/
2tar -czf dfir_platform.spl dfir_platform/

Upload the .spl file through Settings > Install app from file in the Splunk web interface.


SPL Usage Examples

Once installed, the dfir_enrich command is available in any SPL query.

Enrich a Single IOC

spl
1| makeresults | eval ip="203.0.113.47"
2| dfir_enrich type=ip field=ip
3| table ip dfir_risk_score dfir_verdict dfir_sources_queried dfir_sources_flagged

This generates a single event, enriches the IP, and displays the results in a table. Useful for ad-hoc lookups during investigations.

Enrich Firewall Alerts

spl
1index=firewall action=blocked
2| dedup src_ip
3| dfir_enrich type=ip field=src_ip
4| where dfir_risk_score >= 70
5| table _time src_ip dest_ip dest_port dfir_risk_score dfir_verdict
6| sort -dfir_risk_score

This query takes blocked firewall events, deduplicates by source IP (to minimize API calls), enriches each unique IP, filters for high-risk results, and sorts by risk score. The dedup step is important — it ensures you enrich each IP once rather than once per event.

Enrich DNS Query Logs

spl
1index=dns query_type=A
2| dedup query_domain
3| dfir_enrich type=domain field=query_domain
4| where dfir_risk_score >= 50
5| table _time query_domain src_ip dfir_risk_score dfir_verdict dfir_tags

Enrich File Hashes from EDR

spl
1index=edr event_type=file_create
2| dedup file_hash
3| dfir_enrich type=hash field=file_hash
4| where dfir_risk_score >= 40
5| table _time host file_path file_hash dfir_risk_score dfir_verdict

Scheduled Alert with Enrichment

Create a scheduled search that runs every 15 minutes, enriches new suspicious IPs, and triggers an alert for high-risk indicators:

spl
1index=firewall action=allowed dest_port IN (4444, 8443, 9001, 1337)
2| dedup src_ip
3| dfir_enrich type=ip field=src_ip
4| where dfir_risk_score >= 75
5| table _time src_ip dest_ip dest_port dfir_risk_score dfir_verdict

Save this as a scheduled search with alert actions (email, webhook, PagerDuty) for scores above 75. This creates an automated enrichment pipeline that flags high-risk connections without analyst intervention.


Building a Splunk IOC Enrichment Dashboard

Enrichment data becomes more valuable when visualized. Here is a dashboard panel configuration that provides an overview of enriched IOCs.

xml
1<dashboard version="1.1">
2 <label>DFIR Platform IOC Enrichment</label>
3 <row>
4 <panel>
5 <title>High-Risk IOCs (Last 24h)</title>
6 <table>
7 <search>
8 <query>
9 index=firewall action=blocked earliest=-24h
10 | dedup src_ip
11 | dfir_enrich type=ip field=src_ip
12 | where dfir_risk_score >= 70
13 | table src_ip dfir_risk_score dfir_verdict dfir_sources_flagged
14 | sort -dfir_risk_score
15 </query>
16 <earliest>-24h@h</earliest>
17 <latest>now</latest>
18 </search>
19 </table>
20 </panel>
21 </row>
22 <row>
23 <panel>
24 <title>Risk Score Distribution</title>
25 <chart>
26 <search>
27 <query>
28 index=firewall action=blocked earliest=-24h
29 | dedup src_ip
30 | dfir_enrich type=ip field=src_ip
31 | eval risk_category=case(
32 dfir_risk_score>=75, "Critical",
33 dfir_risk_score>=50, "High",
34 dfir_risk_score>=25, "Medium",
35 1=1, "Low")
36 | stats count by risk_category
37 </query>
38 </search>
39 <option name="charting.chart">pie</option>
40 </chart>
41 </panel>
42 </row>
43</dashboard>

Performance and Credit Optimization

Custom search commands execute on the search head for every matching event. Without optimization, a query across a million firewall events would attempt a million API calls. That is neither performant nor economical. Here are the essential optimizations.

Always use dedup before enrichment. The dedup command reduces the event stream to unique IOC values before they reach the enrichment command. If 10,000 firewall events reference 200 unique source IPs, you make 200 API calls instead of 10,000.

The command caches within a search. The dfir_enrich command maintains an in-memory cache for the duration of each search execution. If dedup is not practical for your query, identical IOC values are still only enriched once per search run.

Use earliest and latest to bound your searches. Enriching IOCs from the last hour is more practical than enriching IOCs from the last 30 days. Scope your time ranges to match the operational question.

Monitor credit usage. The DFIR Platform dashboard shows real-time credit consumption. For Splunk deployments with scheduled searches running enrichment, track weekly credit usage to ensure your plan provides adequate capacity.

Plan sizing. The free tier (100 credits/month) supports evaluation and ad-hoc manual lookups. For scheduled enrichment pipelines, the Starter plan (500 credits at $29/mo) handles approximately 100-165 lookups per month. The Professional plan (2,500 credits at $79/mo) supports 500-830 lookups — sufficient for most mid-size SOC operations with well-optimized queries.


Splunk IOC Enrichment App: Security Considerations

API key storage. The example stores the API key in a plaintext configuration file. For production deployments, use Splunk's credential storage API (storage/passwords) to store the key encrypted. Update the load_api_config() function to retrieve the key from Splunk's credential store instead of the flat file.

Network access. The search head must reach api.dfir-lab.ch over HTTPS. In environments with strict egress controls, whitelist the API endpoint. All traffic is encrypted via TLS 1.2+.

Data sensitivity. The enrichment command sends IOC values (IPs, domains, hashes, URLs) to the DFIR Platform API. Review your organization's data handling policies to ensure this is acceptable. The API does not store query data beyond what is needed for rate limiting and billing.

Role-based access. Restrict the ability to run dfir_enrich to roles that should have access to external threat intelligence data. Use Splunk's authorize.conf to control which roles can execute the command.


Conclusion

A Splunk IOC enrichment app built on DFIR Platform's API brings multi-source threat intelligence directly into the SPL pipeline. Analysts enrich indicators without leaving Splunk, scheduled searches flag high-risk IOCs automatically, and dashboards visualize the threat landscape with context from 14+ intelligence sources.

The custom search command takes under 30 minutes to deploy. The in-memory cache and dedup-based workflow optimization keep API usage efficient. And because DFIR Platform uses a single API key and credit pool across all IOC types and capabilities — including phishing analysis, exposure scanning, and AI-assisted triage — the same integration scales to cover new enrichment use cases without additional configuration.

Sign up for a free account at platform.dfir-lab.ch — 100 credits per month, no credit card required. Use code LAUNCH50 for 50% off your first paid month on Starter or Professional plans.

Table of Contents

  • How Splunk Custom Search Commands Work
  • Prerequisites
  • Getting Started
  • Building the Splunk IOC Enrichment App
  • App Directory Structure
  • App Configuration (default/app.conf)
  • Search Command Registration (default/commands.conf)
  • API Configuration (default/dfir_platform.conf)
  • Metadata (metadata/default.meta)
  • The Custom Search Command (bin/dfir_enrich.py)
  • Installing the Splunk IOC Enrichment App
  • Manual Installation
  • Packaging as a .spl File
  • SPL Usage Examples
  • Enrich a Single IOC
  • Enrich Firewall Alerts
  • Enrich DNS Query Logs
  • Enrich File Hashes from EDR
  • Scheduled Alert with Enrichment
  • Building a Splunk IOC Enrichment Dashboard
  • Performance and Credit Optimization
  • Splunk IOC Enrichment App: Security Considerations
  • Conclusion
Share on XShare on LinkedIn
DFIR Platform

Incident Response. Automated.

Analyze phishing emails, enrich IOCs, triage alerts, and generate forensic reports — from your terminal with dfir-cli or through the REST API.

Phishing Analysis

Headers, URLs, attachments + AI verdict

IOC Enrichment

Multiple threat intel providers

Exposure Scanner

Attack surface mapping

CLI & API

Terminal-first, JSON output

Start FreeFree tier · No credit card required

Related Research

WazuhThreat IntelligenceAlert Enrichment+4

DFIR Platform + Wazuh: Real-Time Alert Enrichment

Integrate DFIR Platform's IOC enrichment API with Wazuh for real-time alert enrichment. Includes integratord configuration, active response scripts, and example alert workflows for SOC teams.

Apr 13, 202610 min read
TheHiveIOC EnrichmentCortex+3

DFIR Platform + TheHive: Automated IOC Enrichment for Case Management

Integrate DFIR Platform's multi-source IOC enrichment API with TheHive as a Cortex analyzer. Python code examples, architecture walkthrough, and step-by-step setup for SOC teams.

Apr 12, 202611 min read
oc-enrichmentThreat Intelligencevirustotal+4

VirusTotal API Alternative: Cheaper Multi-Source IOC Enrichment for Security Teams

Apr 15, 20269 min read