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