Detection engineering with Sigma rules: a practical guide
What Sigma is and why it matters
Sigma is a generic signature format for log-based detections. The idea is simple: write a detection once in YAML, then compile it to your SIEM's query language — Splunk SPL, Elastic EQL, Sentinel KQL, Chronicle YARA-L, whatever you're using today, and whatever you might switch to tomorrow.
Without Sigma (or something like it), every detection lives in your SIEM's native syntax. Migrate platforms and you're rewriting rules. Share a detection with someone on a different stack and they can't use it directly. Build a detection library and you've built a Splunk library or an Elastic library, not a detection library.
Sigma separates the what to detect from the how to query. It also gives you a machine-readable format for detection inventory, which matters once you have more than a few dozen rules.
The ecosystem in 2026: The reference implementation is SigmaHQ/sigma. The converter is pySigma + backend packages (e.g. pySigma-backend-splunk). The old sigmac tool is deprecated — use sigma convert from the sigma-cli package.
Anatomy of a Sigma rule
A minimal rule looks like this:
title: Suspicious PowerShell Encoded Command
id: a2a4b654-c3f1-4f29-9e1c-6a2b7d8e0f3a
status: experimental
description: Detects execution of PowerShell with an encoded command argument, often used to obfuscate malicious payloads.
references:
- https://attack.mitre.org/techniques/T1059/001/
author: secureinfra.org
date: 2026/10/15
tags:
- attack.execution
- attack.t1059.001
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\powershell.exe'
CommandLine|contains:
- ' -EncodedCommand '
- ' -enc '
- ' -e '
filter_legitimate:
ParentImage|endswith:
- '\sccm.exe'
- '\wsmprovhost.exe'
condition: selection and not filter_legitimate
falsepositives:
- Configuration management tools (SCCM, Ansible WinRM)
- Some legitimate automation scripts
level: medium
Walk through each section:
- logsource — describes the log type.
category: process_creation+product: windowsmaps to Sysmon Event ID 1, Windows Security 4688, or similar. The backend knows which field names apply. - detection.selection — the conditions to match. Field names here are abstract (
Image,CommandLine) — the backend translates them to your SIEM's field names. - detection.filter_* — exclusions. Always name filters explicitly (
filter_legitimate, not justfilter) so they're self-documenting. - condition — combines selections and filters with boolean logic.
Field modifiers
The |contains, |endswith, etc. in the detection block are field modifiers. They control how the value is compared:
| Modifier | Behavior | Example |
|---|---|---|
contains | substring match | CommandLine|contains: 'whoami' |
startswith | prefix match | Image|startswith: 'C:\Windows\Temp' |
endswith | suffix match | Image|endswith: '\cmd.exe' |
contains|all | all values must be present | CommandLine|contains|all: ['-nop', '-exec', 'bypass'] |
re | regex match | CommandLine|re: '(?i)base64' |
cidr | CIDR range match (for IPs) | DestinationIp|cidr: '10.0.0.0/8' |
The |all modifier deserves attention: a list under a plain modifier matches if any value is present (OR). Adding |all changes it to all values must be present (AND). This is easy to get wrong and produces subtle false negatives if you use the wrong one.
Condition syntax
The condition field takes boolean expressions over the named detection blocks:
# All these are valid conditions:
condition: selection
condition: selection and not filter
condition: selection1 or selection2
condition: 1 of selection* # any block matching pattern "selection*"
condition: all of selection* # all blocks matching pattern
condition: (selection_a or selection_b) and not filter_legitimate
The wildcard pattern (1 of selection*) is useful when you have multiple detection paths for the same technique and want to trigger on any of them without writing long OR chains.
Writing rules that survive production
Start with the filter, not the detection
A detection that fires 10,000 times a day gets disabled. Before you write a Sigma rule for production, audit the false positive surface first: what legitimate processes do the same thing? Run the equivalent query in your SIEM against a week of historical logs and look at the volume and sources.
Add filters for the known-good before you even deploy the rule. It's much easier to tune down from a filtered rule than to suddenly add exclusions to a rule that's been in production firing alerts for three days.
Use specific log sources
The logsource section has an optional service field in addition to category and product:
logsource:
product: windows
service: security
# vs.
logsource:
category: process_creation
product: windows
Using a specific service log (service: security targeting Windows Security event log) gives you access to events that aren't covered by the category mappings. The tradeoff is portability: a rule using service: security with raw event ID fields will only compile correctly for backends that know that log source.
Detection for lateral movement: a worked example
Detecting WMI-based lateral movement (T1021.006) using process creation events:
title: WMI Lateral Movement via WMIC
id: c5a6e9f0-1b2d-4e8a-9c7f-3d4e5f6a7b8c
status: test
description: Detects WMIC spawning a child process on a remote host, a common lateral movement pattern.
logsource:
category: process_creation
product: windows
detection:
selection:
ParentImage|endswith: '\WmiPrvSE.exe'
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\wscript.exe'
- '\cscript.exe'
- '\mshta.exe'
filter_local:
# WmiPrvSE legitimately spawns these for local WMI queries
CommandLine|contains: 'wmiprvse'
condition: selection and not filter_local
tags:
- attack.lateral_movement
- attack.t1021.006
level: high
falsepositives:
- Legitimate remote management via WMI
- Some monitoring tools using WMI
The key observation here is the parent-child relationship: WmiPrvSE.exe (the WMI provider host) spawning an interactive shell process is anomalous for local activity but expected during remote WMI execution. This detection works because we're looking at the relationship, not just the process.
Converting rules: sigma-cli
Install the CLI and a backend:
pip install sigma-cli pySigma-backend-splunk pySigma-backend-elasticsearch
Convert a single rule to Splunk SPL:
sigma convert -t splunk -p splunk_wineventlog rules/windows/process_creation/proc_creation_win_wmi_lateral_movement.yml
Convert a directory of rules, output to a file:
sigma convert \
-t splunk \
-p splunk_wineventlog \
-o detections/splunk_all.spl \
rules/windows/
The -p flag selects a pipeline — a set of field name mappings and transformations. For Splunk, splunk_wineventlog maps Sigma's abstract field names (Image, CommandLine) to what actually shows up in your index. The right pipeline depends on how your log collection is set up — a misconfigured pipeline produces syntactically valid queries that match nothing.
Validating output
Always run converted queries against historical data before deploying. A clean conversion doesn't mean a correct conversion — field name mismatches produce zero results silently.
# Check how many events the rule would have matched in the last 30 days
# (Splunk example)
index=wineventlog EventCode=1 Image="*\powershell.exe" CommandLine="* -EncodedCommand *"
| stats count by host
If the count is zero and you know PowerShell runs in your environment, the field names are wrong. If the count is tens of thousands, you need more filters.
Managing a rule library
Past a few dozen rules, you need to think about rule lifecycle:
- Status field discipline:
experimental→test→stable→deprecated. Don't promote a rule tostableuntil it's survived at least 2 weeks in production without false positives. Only deploystablerules to production alerting. - IDs are permanent: The
idfield is a UUID that should never change. It's how you track a rule across renames and refactors. Generate one once withpython3 -c "import uuid; print(uuid.uuid4())". - Related rules: Use the
relatedfield to link rules that detect the same technique differently — useful when you have high-fidelity and high-recall variants of the same detection.
related:
- id: c5a6e9f0-1b2d-4e8a-9c7f-3d4e5f6a7b8c
type: derived
SIEM integration patterns
Three patterns for getting Sigma rules into your SIEM:
- Static compilation: Convert all rules to queries at deploy time, store the queries in your SIEM. Simple, predictable. Downside: requires a redeploy every time you update rules.
- CI/CD pipeline: Rules live in git. A pipeline converts on merge to main and pushes to the SIEM via API. This is the right answer for teams that change rules frequently — you get version history, review workflows, and automated regression testing.
- Runtime conversion: Some SIEM platforms (Elastic, Chronicle) have native Sigma support or connectors. Rules are stored in Sigma format and converted at query time. Cleanest operationally but introduces conversion latency and backend dependency.
For most teams, option 2 is the right target state. Start with option 1 and migrate when rule churn becomes a maintenance problem.
The SigmaHQ rule repository
The SigmaHQ repository has ~3,000 rules covering Windows, Linux, cloud services, and application logs. Using it as a starting point is reasonable — just don't treat it as a production-ready detection library out of the box.
Before deploying any community rule:
- Check the
falsepositivesfield against your environment - Verify the
levelmatches your noise tolerance - Run it against historical logs and look at the volume
- Understand what the rule is detecting well enough to tune it
A rule that fires on every domain admin logon because someone didn't filter out normal authentication patterns creates alert fatigue faster than having no detection at all.
Further reading: The Sigma specification is the authoritative reference. The original project README by Florian Roth still has the best explanation of the design goals.