Detection-rule generation Turn an IR finding into a portable Sigma rule that any SIEM can consume. Closes the gap between "we noticed something here" and "every host runs a detection for it."
Why this exists
Findings answer "what happened on this host?" Detection rules answer "what should fire on any host when this happens again?" The hand-off between investigation and detection engineering is, in most orgs, a human Slack message. digger automates it: a finding's evidence carries enough structure to write the corresponding Sigma rule mechanically, with a stable UUID, ATT&CK tags, and a reference back to the original case for provenance.
Two paths: per-finding vs per-detector
digger emits Sigma in two distinct flows:
- Per-finding rules (
--case-dir DIR) — walks every finding in the case and emits a rule scoped to that specific observation. Stable UUIDs (SHA-256 of finding content), so re-running on the same case overwrites the same file. - Per-detector templates (
--from-detectors) — emits one generic, class-level rule per detector that implementsto_sigma_template(). No case required. Useful when you want SIEM-deployable detection logic without running a case first.
Run it
# Per-finding mode
digger generate sigma --case-dir ./case-1
# generated 4 Sigma rules from 7 findings
# out: ./case-1/sigma-out
# Just one finding by UUID
digger generate sigma --case-dir ./case-1 --finding ae6b2cac-b996-4a29-bc2e-15e86d4f5412
# Custom output directory
digger generate sigma --case-dir ./case-1 --out-dir ./sigma-rules/
# Per-detector templates (no case needed)
digger generate sigma --from-detectors --out-dir out/sigma/
# generated 13 per-detector Sigma templates
# out: out/sigma
# Verbose: print every written filename
digger generate sigma --case-dir ./case-1 -v
Each rule lands in its own .yml file named
<uuid>_<title>.yml. The UUIDs are deterministic
(SHA-256 of the finding content), so re-running on the same case
overwrites the same files rather than producing duplicates.
What gets mapped
| Finding detector | Sigma log source | What gets emitted |
|---|---|---|
lolbins | process_creation | Image ends with the LOLBin name, CommandLine contains the distinguishing fragment |
suspicious_processes | process_creation | Image + ParentImage + CommandLine selectors |
c2 (cmdline pattern) | process_creation | CommandLine regex from the matched framework pattern |
c2 (IP) | network_connection | DestinationIp selector |
c2 (URL) | proxy | c-uri contains the matched URL fragment |
shai_hulud (package) | file_event | TargetFilename ends with /node_modules/<pkg>/package.json + version filter |
shai_hulud (workflow) | file_event | TargetFilename ends with .github/workflows/shai-hulud-workflow.yml |
env_hijack | process_creation | EnvironmentVariables contains the hijack variable=value |
persistence_outlier | file_event | TargetFilename contains scratch/user-path fragments |
threat_actor | process_creation | CommandLine regex from the attributed TTP pattern |
recon | linux auth / network_connection | SSH brute-force / banner-grab pattern OR count-aggregation of distinct DestinationPort per SourceIp |
exploitation (service→shell) | process_creation | ParentImage ends with /nginx, /httpd, /php-fpm, etc. AND Image ends with /sh, /bash, /cmd.exe, etc. |
exploitation (RCE chain) | process_creation | Image ends with shell name |
exploitation (shellcode cmdline) | process_creation | CommandLine contains all of the pattern's discriminating tokens |
exploitation (weblog) | webserver | cs-uri-query / cs-uri-stem contains the exploit signature (jndi: / classLoader / etc.) |
privesc | file_event / process_creation | TargetFilename + FileMode for setuid; Image=/setcap or /insmod for capability / kernel-module signals |
lateral | network_connection / process_creation / windows.security | Per-kind: outbound RFC1918, credential-dumper cmdline, lateral toolkit Image, SSH ProxyJump, or 4624 + NTLM + anonymous workstation (PtH) |
ad_attacks | windows.security / process_creation | Per-kind: 4769 RC4-HMAC, 4768 PreAuthType 0, 4662 replication-rights GUID, 5136 AdminSDHolder, BloodHound family Image-ends-with |
cloud_attacks | network_connection / process_creation / file_event | Per-kind: 169.254.169.254 DestinationIp, container-escape cmdline, kubeconfig file_event with kube-client filter, cloud-CLI cmdline |
counter_re | process_creation | Image ends with /gdb, /lldb, /x64dbg.exe, etc. AND CommandLine contains target-PID |
persistent_sessions | process_creation / file_event | Per-kind: tmux/screen/zellij parented by network-service Image; user-systemd file_event; nohup/setsid Image |
attacker_tooling | process_creation | Image ends with the specific tool binary name |
Findings whose evidence doesn't map cleanly to a Sigma log source
(memory_anomaly, unsigned_binary, browser
extension permissions, etc.) are silently skipped — the generator
returns None and the CLI reports the count delta.
Per-detector templates (to_sigma_template())
The base Detector class exposes a
to_sigma_template() classmethod. A detector that implements
it declares a generic SIEM rule for the class of behavior it watches —
independent of any specific case finding. The
--from-detectors CLI flag iterates every registered
detector and writes one rule per implementer.
Every Decepticon-countermeasure detector ships a template; the test
tests/test_genrule_detector_templates.py round-trips each
generated rule through SigmaLoader to enforce validity.
Sample output
title: 'Shai-Hulud compromised npm package: chalk@5.6.1'
description: 'package on the worm list.
Auto-generated from digger finding ae6b2cac-b996-4a29-bc2e-15e86d4f5412.'
id: 42ca0822-82c9-4ed2-a356-a8b4c051d4fa
status: experimental
author: digger
date: 2026/05/21
modified: 2026/05/21
references:
- digger-case://sigma-demo/ae6b2cac-b996-4a29-bc2e-15e86d4f5412
level: critical
tags:
- attack.t1195.002
- attack.initial_access
- attack.supply_chain_compromise
falsepositives:
- Investigate context before alerting on this rule.
logsource:
category: file_event
detection:
selection:
TargetFilename|endswith: /node_modules/chalk/package.json
TargetFilename|contains: /node_modules/
filter_version:
TargetFilename|contains: '"version": "5.6.1"'
condition: selection AND filter_version
From digger to your SIEM
The generated rules are standard SigmaHQ format. Feed them through sigma-cli / pySigma to compile to your SIEM's query language:
# Splunk
sigma convert -t splunk -p sysmon ./case-1/sigma-out/*.yml
# Elastic
sigma convert -t lucene -p ecs_windows ./case-1/sigma-out/*.yml
# Microsoft Sentinel (KQL)
sigma convert -t microsoft_sentinel ./case-1/sigma-out/*.yml
# Chronicle (YARA-L)
sigma convert -t chronicle ./case-1/sigma-out/*.yml
The Sigma references field carries
digger-case://<case_id>/<finding_uuid> so the
provenance survives every back-end conversion — if a deployed rule
fires in production, an analyst can trace it back to the original
investigation.
Detection-engineering workflow
# 1. Investigation produces findings on host-of-interest
digger investigate --case-dir ./case-2026-05-21
# 2. Auto-generate Sigma from the findings
digger generate sigma --case-dir ./case-2026-05-21
# 3. Review (pruning over-broad rules, tightening selectors)
# 4. Commit to your detection-rules repo
# 5. CI compiles for your SIEM and deploys
# 6. The same kind of activity now fires on every host in scope
Limits
- Sigma can express log-pattern matching, not arbitrary correlation. Findings that derive their signal from multi-artifact relationships (timeline, persistence outlier with multiple paths) produce simpler one-condition rules — review before deploying.
- Selectors generated from CLI patterns may be over-specific (catches this exact malware, misses tomorrow's variant). The auto-generated rule is a starting point, not a finished detection.
- Sigma's log-source vocabulary is finite. Memory-region anomalies, signature mismatches, and browser-extension permission findings have no canonical log source — these are explicitly not faked.