← Back to all articles
Sigma / SIEM · 12 min read

Sigma to SIEM: pitfalls when translating rules across platforms

Sigma's pitch is "write once, run anywhere," and the backend converters mostly deliver on that — right up until a field mapping silently drops a condition instead of erroring out. A rule that compiles cleanly against one SIEM's field mappings can compile just as cleanly against another and quietly stop matching what it used to match, and nothing about that failure is loud.

The failure mode that doesn't announce itself

The dangerous case isn't a conversion that throws an error. It's a conversion that succeeds, produces a syntactically valid query for the target platform, and returns zero hits because the source telemetry field doesn't exist under the expected name in that platform's pipeline. The query runs every day, an analyst sees "0 results" and reasonably assumes that means "no matches," and the detection has effectively been dead since the day it was migrated.

Where field mappings actually break

Sigma's field mapping layer translates generic field names like CommandLine or ParentImage into whatever your specific log source calls them — process.command_line in one ECS-based pipeline, CommandLine verbatim in a raw Sysmon index, something else entirely in a vendor-normalized schema. A pySigma backend and pipeline that were validated against last year's log schema can silently stop matching after a normalization pipeline upgrade renames or restructures a field, with the conversion step itself giving no indication anything changed.

Modifiers and aggregations don't travel evenly

Not every Sigma backend supports every modifier or aggregation function identically. A rule using |contains|all or a count() aggregation with a timeframe may convert cleanly on one backend and get silently simplified — not rejected, simplified — into a looser match on another, because the target query language doesn't have a direct equivalent and the converter fell back to something close enough to compile. Case sensitivity is another quiet one: a field comparison that's case-insensitive by default in one backend's query language and case-sensitive in another can pass every test you wrote if your test data happens to use consistent casing, then miss real events that don't.

A CI check that catches what compiling won't

The fix isn't more careful manual review at migration time; it's automated regression testing that treats "the query compiles" as necessary but not sufficient. Maintain a small "golden dataset" of known-good (should match) and known-bad (should not match) sample events for every Tier 1 detection, and run each converted query against that dataset as part of CI, on every pipeline or backend version bump — not just at initial deployment.

# conceptual CI step, not a specific tool's syntax
for rule in tier1_rules:
    converted = sigma_convert(rule, target_backend)
    assert run_query(converted, golden_dataset.positive) matches_all
    assert run_query(converted, golden_dataset.negative) matches_none
      

A rule that fails the positive check tells you immediately that a field mapping or modifier broke in translation. A rule that fails the negative check tells you the opposite problem — the conversion loosened instead of tightened, and you're about to inherit a false-positive problem you didn't have before the migration.

This costs real engineering time to build and maintain, and it's worth it exactly in proportion to how much you rely on cross-platform Sigma conversion for your Tier 1 coverage. A one-off personal rule you wrote and tuned directly against your own SIEM doesn't need this scaffolding. A rule set you intend to move across backends, or that other teams will inherit, does.

Comments