Sigma Rules for Linux Threat Detection in 2026: Detection-as-Code with pySigma, Zircolite, and SIEM Backends

Sigma turns Linux auditd, journald, sshd, and sudo events into portable YAML detections that compile to any SIEM. Learn pySigma, sigma-cli, Zircolite, and CI patterns for detection-as-code in 2026.

Updated: August 23, 2026

Sigma is an open, vendor-neutral YAML format for describing log-based detections once and compiling them to any SIEM's query language (Splunk SPL, Elastic ES|QL, Loki LogQL, OpenSearch DSL, Sentinel KQL, and more). On Linux, Sigma rules turn auditd, journald, sshd, and sudo events into portable detection-as-code you can version, unit-test in CI, and ship to any backend without rewriting the logic. I run Sigma across a fleet of roughly 40k Linux hosts, and honestly, it's the only reason our detection library survived three SIEM migrations without a full rewrite.

  • Sigma rules are YAML files with a logsource, a detection block, and a boolean condition. The format is stable, and the SigmaHQ community ships around 3,000 curated rules under the DRL 1.1 license.
  • The classic sigmac Python 2 tool is deprecated. pySigma plus sigma-cli is the maintained path as of 2026, with first-class backends for Splunk, Elastic, Loki, OpenSearch, Sentinel, and Chronicle.
  • Zircolite runs Sigma rules offline against JSON, EVTX, auditd, and journald exports on a laptop. No SIEM required, ideal for IR triage.
  • Wazuh 4.9+ can consume Sigma rules through its integrator; Elastic Security ingests them via the Detection Engine; Grafana Loki uses the sigma-cli Loki backend.
  • Treat detections like code: pre-commit lint with sigma-cli check, unit-test with pytest against event fixtures, and gate merges on ATT&CK tag coverage.
  • The biggest footguns are field-name drift between ingest pipelines and untested rules that silently match zero events after a schema change.

What are Sigma rules and why do they matter on Linux?

Sigma is to SIEM detections what YARA is to file signatures: a portable, human-readable description of a bad thing, decoupled from any particular scanner. Florian Roth and Thomas Patzke published the first spec in 2016, and by 2026 it's the de facto interchange format between detection engineers, SOC analysts, and threat intel vendors. Instead of maintaining one index=linux sourcetype=auditd query in Splunk and a completely different event.category:file AND process.name:"chmod" query in Elastic, you write one Sigma YAML and let pySigma compile it to both.

My threat model for Sigma-on-Linux looks like this. What breaks first when a Linux estate has no shared detection format? Two things. The SOC re-implements the same detection three times (once per SIEM), and detection quality diverges. The Splunk version catches a technique the Loki version silently misses because someone forgot to backport it. Sigma collapses that N×M explosion into an N+M pipeline: N rules, M backends, one source of truth. It also gives you a shared vocabulary with the wider community. SigmaHQ ships thousands of vetted Linux rules covering initial access, privilege escalation, credential access, and lateral movement, all tagged to MITRE ATT&CK. If you're already investing in Linux auditd rules and SIEM integration, Sigma is the layer that makes that investment portable.

Sigma rule anatomy: logsource, detection, condition

Every Sigma rule is a YAML document with a small, stable schema. The three load-bearing sections are logsource (what stream this rule reads), detection (named selection and filter blocks), and condition (a boolean expression combining those blocks). Everything else, title, id, status, author, tags, level, falsepositives, is metadata that the backend uses for triage and rule management. A minimal rule looks like this:

title: Suspicious Curl Piped to Shell
id: 3f6c2b8a-1e5d-4c31-9b45-8e2a1f9d7c40
status: experimental
description: Detects the classic "curl | sh" pattern often used by droppers and installer scripts.
author: Aisha Okonkwo
date: 2026/08/23
references:
  - https://attack.mitre.org/techniques/T1105/
tags:
  - attack.command_and_control
  - attack.t1105
logsource:
  product: linux
  service: auditd
  category: process_creation
detection:
  selection_curl:
    Image|endswith: '/curl'
    CommandLine|contains:
      - ' | sh'
      - ' | bash'
      - ' |sh'
      - ' |bash'
  filter_apt_mirrors:
    CommandLine|contains: 'apt.example.internal'
  condition: selection_curl and not filter_apt_mirrors
falsepositives:
  - Internal package mirrors that legitimately pipe install scripts
level: high

Field modifiers like |contains, |endswith, |startswith, |re, |base64offset|contains, and |cidr are what make Sigma expressive without becoming regex soup. A list under a modifier is an implicit OR; a map with multiple keys is an implicit AND. Combine selections in condition with and, or, not, and aggregate helpers like count() by user > 5. The full grammar is documented in the SigmaHQ specification, and the schema is versioned. Sigma 2 (finalized 2024) added correlation rules that span multiple events, which is what you want for brute-force and low-and-slow detections.

Installing pySigma and sigma-cli on Linux

The legacy sigmac tool that shipped in the original repo was Python 2 and is officially deprecated. As of 2026 the maintained toolchain is pySigma (the library) and sigma-cli (the CLI wrapper), both from SigmaHQ. Install into a virtualenv so backend plugins don't pollute your system Python. I've seen more than one host break its distro python3 because someone pip install'd as root, and rebuilding that box was not a fun afternoon.

# Ubuntu 24.04 / Debian 13 / Rocky 10
sudo apt install -y python3-venv git
python3 -m venv ~/.venvs/sigma
source ~/.venvs/sigma/bin/activate

pip install --upgrade pip
pip install sigma-cli

# Add backends you actually need; each is a separate package
sigma plugin install splunk
sigma plugin install elasticsearch
sigma plugin install loki
sigma plugin install opensearch

# Verify
sigma list backends
sigma version

The sigma plugin command talks to the pySigma plugin directory and installs both the backend (which renders queries) and any pipelines it needs (which map generic Sigma field names to backend-specific field names). Pipelines are the piece nobody explains clearly. Sigma rules use logical field names like Image and CommandLine, but your ingest pipeline probably stores them as process.executable and process.command_line (ECS), or proc.name and proc.cmdline (Falco). The pipeline is the translation layer, and getting it wrong is the number-one reason a "working" rule returns zero hits in production.

Writing your first Linux Sigma rule against auditd

Let's write a rule that catches the pwnkit-shaped pattern: a non-root user executing a SUID binary with a suspiciously short argv. On Linux, the canonical event source is auditd with an execve rule armed. Assuming your auditd rules and SIEM integration already forward SYSCALL+EXECVE records as JSON, the Sigma looks like this:

title: Non-Root SUID Execve With Empty argv[1]
id: 8c4b7f1e-2a19-4b5f-9d84-6ef3a8b91d24
status: experimental
description: |
  Detects a non-root user calling execve on a SUID-root binary with argc <= 1.
  This shape appeared in CVE-2021-4034 (pwnkit) and several follow-on LPEs.
author: Aisha Okonkwo
date: 2026/08/23
tags:
  - attack.privilege_escalation
  - attack.t1548.001
logsource:
  product: linux
  service: auditd
detection:
  selection:
    type: 'SYSCALL'
    syscall: 'execve'
    uid|gt: 0
    euid: 0
  filter_argc:
    argc|gte: 2
  condition: selection and not filter_argc
fields:
  - exe
  - uid
  - auid
  - comm
  - argc
falsepositives:
  - Legitimate SUID helpers invoked with no arguments (rare, investigate each)
level: high

A few things worth calling out here. uid|gt: 0 uses the greater-than modifier introduced in pySigma 0.10, and older tooling would reject it, which is why version pinning matters. The filter_argc block is inverted in the condition with not, giving you the classic "selection minus known-good" pattern. And the fields list tells the backend which event fields to surface in the alert, so your on-call analyst doesn't have to pivot into raw logs just to see who ran what.

Convert it and dry-run against a fixture before shipping:

# Lint first; catches YAML errors and unknown fields
sigma check rules/linux/lpe_suid_short_argv.yml

# Convert to Splunk SPL
sigma convert -t splunk -p sysmon rules/linux/lpe_suid_short_argv.yml

# Convert to Elastic ES|QL with the ECS pipeline
sigma convert -t elasticsearch -p ecs_zeek_beats rules/linux/lpe_suid_short_argv.yml

Converting Sigma to Splunk, Elastic, Loki, and OpenSearch

The conversion command follows the same shape for every backend: sigma convert -t <backend> -p <pipeline> <rule-or-directory>. What differs is the emitted query language and the pipeline you pair with it. Here's the cheat sheet I keep pinned above my desk:

BackendPackageEmitsTypical pipelineNotes
Splunkpysigma-backend-splunkSPL, savedsearches.conf, ES notablesplunk_windows, splunk_linux_indexShips correlation-rule support since 0.11
Elastic Securitypysigma-backend-elasticsearchLucene, EQL, ES|QL, KQL, NDJSONecs_zeek_beats, ecs_windowsEmit NDJSON to import as Detection Engine rules
Grafana Lokipysigma-backend-lokiLogQLloki_okta, loki_promtail_sysmonFalls back to |~ regex for unsupported ops
OpenSearchpysigma-backend-opensearchQuery DSL, monitor JSONecs_* from Elastic backendReuses most Elastic pipelines
Microsoft Sentinelpysigma-backend-microsoft365defenderKQLmicrosoft_365_defenderATT&CK tags flow through to Sentinel's UI

For a directory of rules, point sigma convert at the folder and use -f to pick a file format. For example, -f savedsearches produces a Splunk savedsearches.conf you can drop into a TA, and -f siem_rule_ndjson produces an Elastic NDJSON bundle you can import via the Detection Engine API. The generated queries are deterministic given the same rule + pipeline + backend version, which is what makes CI diffs actually meaningful.

Running Sigma offline with Zircolite for IR triage

Not every situation gives you a SIEM. When I get paged for a suspected compromise on an isolated host, I want to run a few hundred Sigma rules against the local auditd and journald logs on the host itself, with no ingest pipeline and no ES cluster in the loop. Zircolite is a small Python tool that does exactly that. It loads Sigma rules, materializes each event stream into an ephemeral SQLite database, and runs the compiled SQL queries locally. It's my default first move on any Linux incident response engagement.

# Grab the logs from the suspect host (do this from your triage kit)
sudo ausearch --start today --raw > auditd.log
sudo journalctl -o json --since '7 days ago' > journal.json

# Run Zircolite against the auditd log with the Linux ruleset
python3 zircolite.py \
  --evtx auditd.log \
  --ruleset rules/rules_linux_sysmon.json \
  --outfile detections.json \
  --template templates/general_html_report.tmpl \
  --templateOutput report.html

# Same idea against journald
python3 zircolite.py \
  --evtx journal.json \
  --ruleset rules/rules_linux.json \
  --outfile journal-detections.json

Zircolite ships pre-compiled rulesets (Sigma converted with its own SQLite backend), so you don't need sigma-cli on the responder host. It runs on a plain Python 3.10+ interpreter with no external services, which matters when you're working on an air-gapped or half-firewalled machine. Pair it with YARA-X for on-disk malware hunting and Volatility 3 for memory forensics and you have a portable triage stack that fits on a USB stick.

Wiring Sigma into Wazuh, Elastic, and OpenSearch

For production monitoring you want Sigma running continuously against a live event stream, not offline in a Jupyter notebook. Three integrations cover most of the Linux world.

Elastic Security Detection Engine

Convert your ruleset to NDJSON and import via the Detection Engine API. Each Sigma rule becomes an Elastic detection rule with the ATT&CK tags mapped to Elastic's threat framework field. The Elastic Security team even publishes a mapping table showing which Sigma modifiers translate cleanly and which fall back to query_string.

sigma convert -t elasticsearch -f siem_rule_ndjson -p ecs_zeek_beats \
  rules/linux/ -o linux-rules.ndjson

curl -sS -u elastic:$ELASTIC_PW -H 'kbn-xsrf: true' \
  -H 'Content-Type: application/ndjson' \
  --data-binary @linux-rules.ndjson \
  "https://kibana.example:5601/api/detection_engine/rules/_import?overwrite=true"

Wazuh 4.9+

Wazuh doesn't natively parse Sigma YAML, but the community sigma2wazuh converter emits Wazuh XML rulesets from Sigma sources. Point it at your Sigma repo, generate local_rules.xml, and let the Wazuh manager reload. This complements the setup covered in the existing multi-layer Linux IDS with AIDE, auditd, Wazuh, and Suricata walkthrough. Instead of hand-writing Wazuh XML, you author in Sigma and let CI regenerate the XML.

Grafana Loki

The Loki backend emits LogQL that runs inside Grafana Alloy or the Loki ruler. This works surprisingly well for shops that already ship journald to Loki via promtail and don't want a second SIEM standing up alongside it. The catch is that LogQL is line-oriented, so anything requiring cross-event correlation drops back to Loki's metric queries, which are less flexible than Elastic's EQL.

Detection-as-code: CI/CD, testing, and ATT&CK mapping

Detection engineering breaks when rules ship straight from someone's laptop into production. I hit this exact failure mode two jobs ago, and it cost us three weeks of alert fatigue before we cleaned up. The fix is the same pattern application code has used for a decade: version control, PR review, automated tests, and gated deploys. My blast-radius framing here is that a bad detection rule can either miss a real intrusion (silent failure) or generate so many false positives that the SOC starts ignoring the alert channel. Both are catastrophic in different ways, and testing is what keeps you honest.

# .github/workflows/sigma-ci.yml
name: sigma-ci
on: [pull_request]
jobs:
  lint-and-test:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - name: Lint YAML and Sigma schema
        run: sigma check rules/
      - name: Convert to all target backends (must not error)
        run: |
          sigma convert -t splunk        -p splunk_linux_index rules/ > /dev/null
          sigma convert -t elasticsearch -p ecs_zeek_beats     rules/ > /dev/null
          sigma convert -t loki          -p loki_promtail_sysmon rules/ > /dev/null
      - name: Run rule unit tests against fixture events
        run: pytest tests/
      - name: Fail if any rule lacks ATT&CK tags
        run: python tools/require_attack_tags.py rules/

The unit tests use pySigma's Python API to compile each rule into a callable predicate, then assert that a "true positive" fixture event matches and a "known good" fixture does not. This is where you catch schema drift: when the ingest pipeline renames process.name to process.executable.name, the test fails in CI, not silently in production three weeks later. For the ATT&CK check, walk the rules and assert every one has at least one attack.* tag. That's how you get coverage heatmaps that your CISO can actually read.

Pair Sigma with your existing telemetry from osquery fleet monitoring and Zeek network security monitoring. Zeek gives you network events, osquery gives you host state snapshots, and Sigma is the language you use to write detections across all three feeds without picking a favorite backend.

Common Sigma rule pitfalls and how to avoid them

After a few years of running this at scale, the failure modes are boringly consistent. Watch for these:

  • Field-name drift. The Sigma rule says CommandLine, the ingest pipeline stores process.command_line, and nobody wrote a pipeline mapping. Symptom: rule returns zero hits forever. Fix: mandatory unit tests with realistic fixture events.
  • Over-broad selections. CommandLine|contains: 'chmod' matches every configuration-management run in the fleet. Symptom: 5,000 alerts per day. Fix: pair every keyword selection with a specificity constraint (parent process, user context, path prefix).
  • Untested regex. A single |re modifier with a catastrophic backtracking pattern can DoS the Splunk search head. Fix: run rxxr2 or a similar ReDoS linter in CI.
  • Missing filter for internal tooling. The rule catches the attacker and your own SOAR playbook. Fix: keep an "allowlist" filter block per rule and document its assumptions.
  • ATT&CK tag rot. Sub-technique IDs change; MITRE renumbered several in the 2024 update. Fix: pin an ATT&CK version and run the SigmaHQ attack-taxonomy validator in CI.
  • Ignoring correlation rules. Single-event rules can't express "5 failed sudo attempts followed by a success within 60 seconds." Fix: use Sigma 2 correlation rules where the backend supports them (Splunk, Elastic, Sentinel all do as of 2026).

Frequently Asked Questions

What are Sigma rules used for?

Sigma rules describe log-based detections in a vendor-neutral YAML format. You write the detection once and compile it to any SIEM's query language (Splunk SPL, Elastic ES|QL, Loki LogQL, OpenSearch DSL, Sentinel KQL), so the same logic runs everywhere. On Linux, they cover process execution, authentication, file integrity, and network telemetry.

Is Sigma still maintained in 2026?

Yes. SigmaHQ is actively maintained, and the Sigma 2 specification (finalized 2024) added correlation rules and stronger typing. The legacy Python 2 sigmac tool is deprecated in favor of pySigma and sigma-cli, both under active development with regular backend releases.

Can Sigma rules run on Linux logs like auditd and journald?

Yes. Set logsource: {product: linux, service: auditd} (or journald, sshd, sudo, cron) in the rule header, and use the matching backend pipeline. SigmaHQ ships hundreds of Linux-specific rules under rules/linux/, and Zircolite can run them offline against exported log files.

What is the difference between Sigma and YARA?

YARA describes patterns in files or process memory (byte strings, imports, entropy). Sigma describes patterns in log events (process execution, authentication, network flows). They complement each other: YARA is your on-disk and in-memory scanner, Sigma is your log-based detection language, and both should live in the same detection-as-code repository.

How do you convert a Sigma rule to Splunk SPL?

Install the Splunk backend with sigma plugin install splunk, then run sigma convert -t splunk -p splunk_linux_index rule.yml. The -p flag selects the pipeline that maps Sigma's generic field names to your Splunk sourcetype fields. Using the wrong pipeline is the most common source of empty search results.

Does Wazuh support Sigma rules natively?

Not directly. Wazuh consumes its own XML rule format. Use the community sigma2wazuh converter to generate Wazuh XML from Sigma sources, run that conversion in CI, and deploy the resulting local_rules.xml to the Wazuh manager. This lets you keep a single Sigma source of truth even in a Wazuh-heavy environment.

Aisha Okonkwo
About the Author Aisha Okonkwo

Infrastructure security architect at a hyperscaler. Spends her days on Zero Trust, secrets management, and yelling at unencrypted backups.