Skip to content

feat(sigma): ship the fifteen sequence detections as Sigma rules - #98

Merged
blitzcrieg1 merged 1 commit into
masterfrom
feat/sigma-sequence-pack
Aug 23, 2026
Merged

feat(sigma): ship the fifteen sequence detections as Sigma rules#98
blitzcrieg1 merged 1 commit into
masterfrom
feat/sigma-sequence-pack

Conversation

@blitzcrieg1

Copy link
Copy Markdown
Owner

The Sigma pack covered the recorder's own health — heartbeat silence, degraded coverage, MCP schema drift, denial bursts, untriaged criticals. All useful, none of it the product.

The fifteen sequence detections, which are what a SOC actually routes on, had no Sigma representation at all. A Splunk or Sentinel team wanting to alert on credential-exfil had to write the search themselves from a schema document.

That is a strange gap for a project whose pitch is "your SIEM stays the console." If the console is theirs, the content has to be portable.

Generated, not written

Hand-writing fifteen would put rule ids, severities and MITRE ids in a second place that drifts from the first — the failure this repo keeps finding in its own documents. tools/generate_sigma_pack.py replays the benchmark corpus through the real engine and reads the Detection objects it emits, so a severity that changes in rules.py changes here on the next run.

detection:
  selection:
    action.type: detection
    detection.rule_id: credential-exfil
  condition: selection
level: critical
tags: [attack.ta0006, attack.ta0011, attack.t1071.001, attack.t1552.004]

Sixteen files for fifteen rules, and the extra one is a real finding

encoded-command-download emits two severities on purpose: critical for remote code fetched and executed, low for local content piped into an interpreter, with a comment in rules.py saying the low one exists "so it stops drowning the criticals".

Keying the pack on rule_id alone produced a single rule at whichever severity the corpus yielded first — it claimed critical while some firings are low. A test caught it:

AssertionError: encoded-command-download: engine says low, Sigma says critical

Each severity now gets its own rule with the selection pinned on action.outcome. One static level would either page on the quiet variant or stay silent on the loud one.

Five tests

Every built-in rule has a Sigma rule; no Sigma rule outlives a deleted one; severities match what the engine emits; the documents are valid Sigma; and the UUIDs do not churn on regeneration (they are derived from the rule id, so a consumer who pinned one keeps working).

The two with no corpus case

host-subagent-swarm-burst needs several sessions on one host. off-hours-activity is opt-in behind an env var and a window. Both carry an explicit entry in the generator with the reason, and that table is checked against BUILTIN_RULE_IDS rather than trusted — a sixteenth rule with neither a corpus case nor an entry fails the script instead of silently shipping a pack that claims more than it has. Tracked in #36.

Also

CI now lints tools/, since the generator lives there and emits rules a SOC routes on. That surfaced one pre-existing S101 in mcp_audit_proxy.py — a type-narrowing assert after create_subprocess_exec — annotated rather than rewritten.

  • 1125 tests, ruff clean
  • Ruleset fingerprint unchanged at 56ad3de1, so the dogfood clock is untouched (the generator reads rules.py, never writes it)

🤖 Generated with Claude Code

The Sigma pack covered the recorder's own health: heartbeat silence, degraded
coverage, MCP schema drift, denial bursts, untriaged criticals. All useful and
none of it the product. The fifteen sequence detections, which are the thing a
SOC would route on, had no Sigma representation at all, so a Splunk or Sentinel
team wanting to alert on credential-exfil had to write the search themselves
from a schema document.

That is a strange gap for a project whose pitch is that your SIEM stays the
console. If the console is theirs, the content has to be portable.

Generated rather than written. Hand-writing fifteen would put rule ids,
severities and MITRE ids in a second place that drifts from the first, which is
the failure this repository keeps finding in its own documents. The generator
replays the benchmark corpus through the real engine and reads the Detection
objects it emits, so a severity that changes in rules.py changes here on the
next run.

Sixteen files for fifteen rule ids, and the extra one is a real finding.
encoded-command-download emits two severities on purpose: critical for remote
code fetched and executed, low for local content piped into an interpreter,
with a comment in rules.py saying the low one exists "so it stops drowning the
criticals". Keying the pack on rule_id alone produced a single rule at whichever
severity the corpus happened to yield first, so it claimed critical while some
firings are low. A test caught it. Each severity now gets its own rule with the
selection pinned on action.outcome, because one static level would either page
on the quiet variant or stay silent on the loud one.

Rule UUIDs are derived from the rule id rather than generated, so rerunning is
idempotent and a consumer who pinned an id keeps working. Severity joins the
seed only for a split rule, which keeps the fourteen unsplit ids where they are.

Five tests: every built-in rule has a Sigma rule, no Sigma rule outlives a
deleted one, severities match what the engine emits, the documents are valid
Sigma, and the ids do not churn on regeneration.

Two of the fifteen have no corpus case and carry an explicit entry in the
generator with the reason. host-subagent-swarm-burst needs several sessions on
one host; off-hours-activity is opt-in behind an env var and a window. The
table is checked against BUILTIN_RULE_IDS rather than trusted, so a sixteenth
rule with neither a corpus case nor an entry fails the script instead of
silently shipping a pack that claims more than it has. Tracked in #36.

CI now lints tools/ as well, since the generator lives there and emits rules a
SOC routes on. That surfaced one pre-existing S101 in mcp_audit_proxy.py, a
type-narrowing assert after create_subprocess_exec, annotated rather than
rewritten.

1125 tests, ruff clean, ruleset fingerprint unchanged at 56ad3de1 so the
dogfood clock is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@blitzcrieg1
blitzcrieg1 merged commit 131b2dc into master Aug 23, 2026
9 checks passed
@blitzcrieg1
blitzcrieg1 deleted the feat/sigma-sequence-pack branch August 23, 2026 09:25
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant