Write Sigma Rules That Convert Reliably Across SIEM Backends
For developers and detection engineers who need Sigma rules to compile cleanly into multiple SIEM query languages without hand-editing every backend. This shows a repeatable authoring workflow, validation commands, and the rule patterns that survive translation best across Splunk, Elastic, Sentinel, and other common targets.
TL;DR — Write Sigma against normalized fields, keep detections to simple boolean logic, avoid backend-specific regex and aggregation unless you have tested every target, and validate with
sigma-clibefore committing. The single most common fix is replacing source-specific field names and unsupported modifiers with Sigma core fields plus explicitlogsourcemetadata. Reading time: ~5 min
Goal
When you finish, you will have a Sigma rule that passes validation, converts with sigma-cli into at least two SIEM backends without errors, and produces equivalent query logic instead of backend-specific breakage.
Prerequisites
- A workstation with Python 3.11+ — check with:
python3 --version
sigma-cliinstalled — check with:
sigma version
- If missing, install it with:
python3 -m pip install --upgrade pip pysigma sigma-cli
- A text editor and shell access
- Sample logs or known field mappings for the products you target
- At least two target backends to test, for example
splunkandes-qs - A Git repo or working directory for your rules
- The Sigma rule specification conventions your team follows, especially field naming and false-positive handling
Steps
Step 1: Install and confirm the Sigma toolchain
Run:
python3 -m pip install --upgrade pip pysigma sigma-cli
sigma version
sigma plugin list
You should see a version line and a plugin list; success looks like a normal zero-exit command with backend names in the output.
Example output shape:
$ sigma version
Sigma CLI 1.0.x
$ sigma plugin list
Installed plugins:
- splunk
- elasticsearch
- sentinel
- microsoftxdr
Step 2: Create a rule skeleton with explicit metadata
Create rules/process_suspicious_rundll32.yml with exactly this content:
title: Suspicious Rundll32 Loading JavaScript URL
id: 2d9d4d2d-5b0d-4f5d-9e2d-3f0e7d4c9a11
status: test
description: Detects rundll32 executing remote script content via javascript handler.
references:
- https://lolbas-project.github.io/lolbas/Binaries/Rundll32/
author: Your Team
date: 2026-10-30
logsource:
category: process_creation
product: windows
detection:
selection_img:
Image|endswith: '\\rundll32.exe'
selection_cli_1:
CommandLine|contains: 'javascript:'
selection_cli_2:
CommandLine|contains: 'http'
condition: selection_img and all of selection_cli_*
falsepositives:
- Administrative scripts using rundll32 with scripted handlers
level: high
You should see a saved YAML file with no tabs and no editor warnings.
Step 3: Validate the rule before converting it
Run:
sigma validate rules/process_suspicious_rundll32.yml
You should see a success message or no error output and exit code 0.
If the YAML is broken, the output typically looks like:
$ sigma validate rules/process_suspicious_rundll32.yml
[ERROR] YAML parsing error in rules/process_suspicious_rundll32.yml: mapping values are not allowed here at line 14, column 14
Step 4: Convert the rule into two backends
Run:
sigma convert -t splunk rules/process_suspicious_rundll32.yml
sigma convert -t es-qs rules/process_suspicious_rundll32.yml
You should get two query strings, not stack traces or "unsupported modifier" errors.
Typical output shape:
$ sigma convert -t splunk rules/process_suspicious_rundll32.yml
(Image="*\\rundll32.exe" CommandLine="*javascript:*" CommandLine="*http*")
$ sigma convert -t es-qs rules/process_suspicious_rundll32.yml
Image:*\\rundll32.exe AND CommandLine:*javascript:* AND CommandLine:*http*
Step 5: Rewrite fields that are not portable
If your first draft used backend-native fields like process.name, NewProcessName, or winlog.event_data.Image, replace them with Sigma-level fields in the rule body and keep source context in logsource.
Use this pattern:
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\\powershell.exe'
CommandLine|contains: '-enc'
condition: selection
Not this pattern:
detection:
selection:
process.name: 'powershell.exe'
winlog.event_data.CommandLine|contains: '-enc'
condition: selection
You should see the converted queries stop leaking source-schema field names that only exist in one pipeline.
Step 6: Keep logic inside the portable subset
Use only these patterns unless you have backend tests proving more complex behavior works everywhere:
detection:
sel_img:
Image|endswith:
- '\\cmd.exe'
- '\\powershell.exe'
sel_cli:
CommandLine|contains:
- ' whoami '
- ' ipconfig '
filter_parent:
ParentImage|endswith: '\\trusted-installer.exe'
condition: 1 of sel_* and not filter_parent
Avoid these in cross-platform rules unless required and tested per backend:
detection:
selection:
CommandLine|re: '(?i)foo.*bar'
timeframe: 5m
condition: count(selection) by Computer > 5
You should see fewer conversion differences and fewer backend-specific edits after generation.
Step 7: Convert all rules in CI and fail on errors
Add a shell check to your repo, for example scripts/check-sigma.sh:
#!/usr/bin/env bash
set -euo pipefail
for rule in rules/*.yml; do
echo "Validating $rule"
sigma validate "$rule"
echo "Converting $rule -> splunk"
sigma convert -t splunk "$rule" >/dev/null
echo "Converting $rule -> es-qs"
sigma convert -t es-qs "$rule" >/dev/null
done
Run:
chmod +x scripts/check-sigma.sh
./scripts/check-sigma.sh
You should see every rule validate and convert with no nonzero exit code.
⚠️ If you replace existing production rules with newly normalized ones, query semantics can change and detections can go quiet or get noisier. Convert and compare queries in a test environment before promoting them to the SIEM content repo.
Step 8: Compare generated queries for semantic drift
Generate backend outputs into files:
mkdir -p build
sigma convert -t splunk rules/process_suspicious_rundll32.yml > build/rundll32.splunk.txt
sigma convert -t es-qs rules/process_suspicious_rundll32.yml > build/rundll32.esqs.txt
sed -n '1,5p' build/rundll32.splunk.txt
sed -n '1,5p' build/rundll32.esqs.txt
You should see the same core constraints in both files: image match, javascript:, and http.
View as text
[Rule YAML] ---- 1. validate ----> [sigma-cli] [sigma-cli] ---- 2. convert splunk ----> [Splunk query] [sigma-cli] ---- 3. convert es-qs ----> [Elastic query] [Developer] ---- 4. compare logic ----> [Equivalent detection]
Verify it works
Run all of these:
sigma validate rules/process_suspicious_rundll32.yml
sigma convert -t splunk rules/process_suspicious_rundll32.yml >/tmp/rule.spl
sigma convert -t es-qs rules/process_suspicious_rundll32.yml >/tmp/rule.es
wc -c /tmp/rule.spl /tmp/rule.es
grep -E 'rundll32|javascript|http' /tmp/rule.spl /tmp/rule.es
Expected result:
sigma validateexits0- both
sigma convertcommands exit0 - both output files are non-empty
grepfinds all three detection terms in both backend outputs
A healthy output shape looks like:
$ wc -c /tmp/rule.spl /tmp/rule.es
86 /tmp/rule.spl
79 /tmp/rule.es
165 total
$ grep -E 'rundll32|javascript|http' /tmp/rule.spl /tmp/rule.es
/tmp/rule.spl:(Image="*\\rundll32.exe" CommandLine="*javascript:*" CommandLine="*http*")
/tmp/rule.es:Image:*\\rundll32.exe AND CommandLine:*javascript:* AND CommandLine:*http*
Common pitfalls
Using source-specific field names in the rule body
Mistake: writing process.name, winlog.event_data.Image, or vendor pipeline fields directly in detection.
Symptom: conversion succeeds for one backend but produces useless queries or unmapped fields for another.
Fix: replace them with Sigma common fields like Image, CommandLine, ParentImage, and move source context into logsource.
Using unsupported modifiers like |re everywhere
Mistake: relying on regex modifiers for detections that could be plain substring or suffix matches.
Symptom: sigma convert fails with an error similar to:
[ERROR] Unsupported value modifier: re
Fix: rewrite with |contains, |startswith, |endswith, or explicit value lists.
Writing aggregation or correlation into a single portable rule
Mistake: adding count(...) by host > N, long timeframe logic, or correlation assumptions to a rule expected to work across all backends.
Symptom: one backend converts, another rejects the rule or changes semantics.
Fix: keep the Sigma rule atomic and implement correlation in the destination SIEM where needed.
Omitting logsource or making it too vague
Mistake: leaving out category and product, or using a generic source that does not imply the event type.
Symptom: field mappings become ambiguous and generated queries target the wrong dataset.
Fix: set literal values such as:
logsource:
category: process_creation
product: windows
YAML formatting errors from tabs or unescaped backslashes
Mistake: using tabs for indentation or writing Windows paths with single backslashes.
Symptom: validator errors like mapping values are not allowed here or string parsing failures.
Fix: use spaces only and escape backslashes, for example \\powershell.exe.
Assuming equivalent semantics after successful conversion
Mistake: stopping after sigma convert returns exit code 0.
Symptom: both backends compile, but one query broadens matching because of wildcard handling or field analyzer behavior.
Fix: save both generated queries to files and compare the actual terms and operators before shipping.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI