Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Detection-as-code treats detection logic the same way software engineering treats application code: rules live in version control, changes go through pull-request review, an automated pipeline lints and tests them, and deployment to production SIEM/EDR happens through the pipeline rather than by an analyst clicking “save” in a vendor UI. Sigma, the generic, YAML-based signature format maintained by SigmaHQ, is the format that makes this practical across heterogeneous back ends: a rule is written once against a normalized log-source taxonomy and converted at build time into whatever query language the target SIEM speaks (Splunk SPL, Microsoft Sentinel KQL, Elastic EQL/Lucene, and others) via pySigma/sigma-cli backends and pipelines.
The payoff is the same one software teams get from CI/CD: every change is reviewable and auditable (who changed this rule and why), every change is testable before it reaches production (did this actually still fire on the sample event, did the syntax lint clean), rollbacks are a git revert instead of manual reconstruction, and rule quality stops depending on tribal knowledge of one analyst’s SIEM login. It also directly supports coverage mapping — rules can be tagged with ATT&CK technique IDs and that metadata becomes queryable in the repo itself.
Attack Prerequisites
Standing up a detection-as-code pipeline needs the following in place before the first rule is committed:
- A git repository for rules, structured by log source or tactic (
rules/windows/process_creation/,rules/cloud/aws/, etc.), plus atests/directory of sample events. pySigma/sigma-cliinstalled in CI, with the backend package for each target platform (pysigma-backend-splunk,pysigma-backend-elasticsearch,pysigma-backend-microsoft365defender, etc.).- A conversion pipeline for each backend — pySigma “pipelines” map generic Sigma field names (
Image,CommandLine) onto the target’s actual schema (e.g. ECSprocess.name,process.command_line). - A deployment credential/API for the target SIEM (Splunk saved-search REST endpoint, Elastic Security detection-rules API, Sentinel Analytics Rules ARM/REST API) scoped to a CI service account, not a personal login.
- A CI runner (GitHub Actions, GitLab CI, Azure DevOps) with secrets management for that credential.
How It Works
A Sigma rule is a YAML document with a fixed shape: logsource (which product/service/category the rule targets, e.g. product: windows, service: security), detection (one or more named selection/filter blocks of field: value matches plus a boolean condition combining them), and metadata (level, falsepositives, tags, id as a UUID, status). The abstraction is deliberately close to what analysts already think in — “this field equals this value, and not that other thing” — while staying backend-agnostic.
sigma-cli (built on pySigma) does the actual conversion: sigma convert -t splunk -p sysmon rules/process_creation/proc_susp_encoded_cmd.yml reads the rule, applies the named pipeline to translate field names for that backend, and emits a working SPL search. sigma check validates syntax and schema without needing a backend at all, which is what a CI lint stage runs on every pull request before anyone reviews the logic. Because pipelines are separate, swappable artifacts, the same rule file produces correct queries for Splunk, Elastic, and Sentinel without three copies of the logic to keep in sync.
A typical pipeline has four stages: lint (sigma check, plus custom checks like “tags must include an attack.tXXXX”), convert (render the rule for every configured backend), test (replay the converted query — or, for backends that support it, the raw rule — against a fixture set of recorded sample events and assert expected matches/non-matches), and deploy (push the converted, tested query to the SIEM via its API, only on merge to the main branch).
Practical Example / Configuration
A real Sigma rule detecting suspicious Base64-encoded PowerShell execution, one of the most common Sigma community patterns:
title: Suspicious Encoded PowerShell Command Line
id: 1f5e1433-9f19-4187-9f4e-0e5b3d5b2b57
status: test
description: Detects PowerShell invoked with -EncodedCommand / -enc
logsource:
category: process_creation
product: windows
detection:
selection_img:
Image|endswith:
- '\powershell.exe'
- '\pwsh.exe'
selection_flag:
CommandLine|contains:
- '-enc '
- '-EncodedCommand'
- ' -e '
condition: selection_img and selection_flag
falsepositives:
- Legitimate admin scripts using EncodedCommand for quoting safety
level: medium
tags:
- attack.execution
- attack.t1059.001
YAMLAnd the CI workflow that lints, converts, and gates on it (GitHub Actions):
name: sigma-ci
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: {python-version: '3.11'}
- run: pip install sigma-cli pysigma-backend-splunk
- name: Lint all rules
run: sigma check rules/
- name: Require ATT&CK tag
run: |
for f in $(find rules -name '*.yml'); do
grep -qE 'attack\.t[0-9]{4}' "$f" || \
{ echo "missing ATT&CK tag: $f"; exit 1; }
done
- name: Convert (dry run)
run: sigma convert -t splunk -p sysmon rules/ -O
YAMLWalkthrough / Exploitation
Rolling this out on top of an existing, ad-hoc rule set:
# 1. Seed the repo from existing SIEM content, one YAML per rule
sigma-cli/scripts/backport_from_splunk.py --app my-siem-app > rules/
# 2. Add fixtures: recorded/synthetic events each rule should match
mkdir -p tests/proc_susp_encoded_cmd/
cp sample_events/encoded_ps.json tests/proc_susp_encoded_cmd/positive.json
# 3. Wire pre-commit so lint failures never even reach a PR
pre-commit install
echo '- repo: local\n hooks:\n - id: sigma-check\n \
entry: sigma check rules/\n language: system' >> .pre-commit-config.yaml
# 4. On merge, deploy stage converts + pushes via the SIEM's REST API
sigma convert -t splunk -p sysmon rules/process_creation/*.yml -O \
| ./deploy_to_splunk.sh --app my-siem-app --sourcetype WinEventLog:Security
BashVersion pin the Sigma schema and the pipeline package in requirements.txt (pysigma==0.11.*) — pipelines occasionally change field mappings between releases, and an unpinned upgrade silently changing a converted query is exactly the kind of regression detection-as-code is meant to prevent.
Note: Sigma’s
relatedfield (related: [{id: <uuid>, type: derived}]) is worth using from day one — it tracks rule lineage when you split or tighten a noisy rule, which matters when someone later asks “why do we have three rules for the same technique.”
Opsec: Treat the rules repository itself as sensitive: it is a precise map of what you detect and, by omission, what you don’t. Keep it in a private repo with access limited to the detection engineering team, and never commit real SIEM API credentials — use the CI platform’s secrets store.
Detection and Defense
Detection-as-code is a defensive practice in itself, but the pipeline needs its own guardrails:
- Required review — branch protection requiring at least one analyst approval before merge to main, same as application code.
- Fixture-based testing — every rule ships with at least one positive and one negative sample event; CI fails if the converted query doesn’t match as expected.
- Staleness tracking — a scheduled job that flags rules with
status: testolder than N days, or rules that haven’t fired in production in N days, for re-validation. - Secrets scanning on the repo — the rules repo itself should be scanned (gitleaks/trufflehog) since analysts sometimes paste real log samples containing credentials into fixtures.
- Metrics as code review criteria — MTTD and false-positive rate per rule tracked alongside the rule file, so tuning happens via PR, not manual SIEM edits that drift from the repo.
Real-World Impact
Sigma, maintained by SigmaHQ (the community project continuing Florian Roth/Neo23x0’s original work), is the closest thing the industry has to a portable detection format and is consumed as an import/export format by numerous SIEM and XDR vendors; Elastic’s detection-rules repository follows a near-identical version-controlled, CI-tested model for its own native rule format. Detection-as-code as a discipline is explicitly described in Palantir’s public “Alerting and Detection Strategy” (ADS) framework and is now the default expectation in mature detection engineering job descriptions, precisely because SIEM-UI-only rule management does not scale past a handful of analysts without losing auditability.
Conclusion
Detection-as-code is not about Sigma specifically — it is about applying software engineering discipline (version control, review, automated testing, staged deployment) to detection content that too often lives only in a SIEM UI where changes are undocumented and untested. Sigma makes it concretely achievable because it decouples rule logic from the backend, but the pipeline discipline — lint, tag, test against fixtures, deploy via API, track staleness — is what actually keeps detection quality from decaying as a team and rule set grow.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments