Sigma is the closest thing detection engineering has to a common rule format — plain YAML, vendor-neutral, with a large community-maintained rule library. What Sigma isn't is a single reference implementation. The spec defines syntax and semantics; every platform that "supports Sigma" has actually written its own evaluator, and each one covers a different subset of the full grammar. This post is about the subset we chose and why.
What a Sigma rule actually asks for
A rule has a logsource (what kind of log this applies to — product, service, category) and a detection block defining one or more named selections and a condition combining them:
logsource:
product: windows
category: process_creation
detection:
selection:
Image|endswith: '\regsvr32.exe'
CommandLine|contains|all:
- '/i:'
- 'scrobj.dll'
condition: selection
That's a field-match condition — check specific fields against specific values, with modifiers like endswith, contains, all. The full spec also supports aggregation conditions (count() by field > N), timeframes, and near-term Sigma correlation rules that reference multiple base rules together.
What we implemented, and what we didn't
XCloak's engine handles field-level matching and logsource filtering completely — that's the majority of rules in the public Sigma rule repository, and it's the part that maps cleanly onto "does this one log line match this one condition," which is exactly the question we need answered on the synchronous ingest path described in the previous post.
We do not implement Sigma's aggregation conditions (count() by ... > N over a timeframe) inside the rule engine itself. That's not an oversight — aggregation conditions require state across multiple events over a window, which is a fundamentally different computational shape than "evaluate this one event against this one condition," and forcing it into the same code path would have meant either slowing down every field-match evaluation to accommodate stateful rules, or building two evaluators that pretend to be one. Instead, that class of detection is handled by the dedicated behavioral detectors — purpose-built, scheduled jobs for things like credential-stuffing (N failed logins from M source IPs) or port-scan detection (N distinct ports from one source in a window), which is a clearer way to express "this needs to track state over time" than shoehorning it into a rule format designed for single-event matching.
Logsource pre-filtering
Every rule declares a logsource. Before any field condition is even evaluated, we bucket the tenant's active ruleset by log format at cache-build time — so a rule tagged product: windows never gets considered against a line that normalized in as a Linux auth log. This sounds obvious in isolation, but it matters a lot in aggregate: a tenant with a few hundred active rules doesn't pay the cost of evaluating all of them against every log; they only pay for the subset that could plausibly match.
Why cache per tenant
Rules are tenant-scoped — every tenant can have a different active ruleset, with different custom rules layered on top of the 102 seeded ones. Re-parsing YAML and rebuilding the condition tree on every single log evaluation would be wasteful and pointless, since the ruleset only changes when someone edits a rule. So the cache is invalidated on write (rule created, updated, deleted) and otherwise reused across every log that tenant sends — meaning most ingest requests pay zero YAML-parsing cost at all.
What this buys you, and what to know before relying on it
If you're evaluating XCloak against a Sigma-native SIEM, the honest comparison point is: field-match and logsource-filter conditions work and are unit-tested, which covers a large share of publicly available Sigma rules. Full aggregation-condition support inside the rules engine itself isn't there — those detection patterns exist in XCloak, just implemented as dedicated detectors rather than as generic Sigma aggregation rules. If you're bringing over a large existing Sigma rule library from another platform, it's worth checking whether your rules lean on aggregation conditions before assuming a drop-in migration.