Architecture
detsema is structured like a compiler: frontends parse rule dialects into one typed IR, passes answer questions about the IR, emitters render findings.
frontends/sigma Sigma YAML -> IR (documented subset; explicit unsupported verdicts)
ir typed predicate trees + reference evaluator Eval
passes/deps field-dependency extraction
passes/check schema verdicts: unknown-field, case-mismatch, type-mismatch, unsupported
schema detsema-schema/v1 inventories: load, infer from NDJSON, import ECS
emitters/sarif,jsonl SARIF 2.1.0 and JSON-lines output
cmd/detsema CLI and exit codes
IR
Rules compile to boolean trees (And, Or, Not) over leaf predicates. A predicate applies
one typed matcher to one event field:
| Matcher | Carries | From Sigma |
|---|---|---|
StringMatch |
anchor (exact/contains/prefix/suffix), pattern, wildcard structure, case flag, windash | plain values, contains, startswith, endswith, cased, windash |
NumCompare |
operator, bound | lt, lte, gt, gte |
RegexMatch |
compiled RE2 | re (+ i, m, s) |
CIDRMatch |
parsed prefix | cidr |
ExistsMatch |
presence assertion | exists, field: null |
Matchers are structured data, not closures, so passes can reason about them by type: numeric
intervals from NumCompare, prefix/suffix compatibility from StringMatch, value domains from
schema enums. This is what lets the planned satisfiability pass work without an SMT solver.
Eval(node, event) is the reference semantics for the whole toolchain. The semantic decisions
(case-insensitive matching by default, wildcard escaping, null-means-absent, list values match
on any element, dotted field lookup) are specified in
SUBSET.md.
Pass contract
A pass consumes IR plus an optional schema inventory and emits findings with rule id, title, file, line, severity and a stable type. Two rules are load-bearing:
- No silent approximation. A construct the frontend cannot represent exactly becomes an
unsupportedverdict naming the construct. Parseable fragments of such rules still contribute dependency data, marked partial. - Witness verification. Any future pass that emits a witness event (“this event matches rule
A but not rule B”) must check the claim with
Evalbefore emitting it.
Schema inventories
An inventory (detsema-schema/v1) declares fields with types (string, number, bool,
ip, timestamp, object, unknown) and optional closed enum domains. Every verdict is
relative to a declared inventory and reports name the inventory source. Sources: inferred from
NDJSON event samples, imported from ECS, hand-written, or (planned) exported from live cluster
mappings by deadair.
Safety properties
- No network access at runtime; only the files named on the command line are read.
- No telemetry, no phone-home.
- Inputs are untrusted: malformed YAML/JSON produces clean errors; rule patterns compile to RE2 (linear-time matching).
- Runtime dependency surface is one YAML parser; growing it is an explicit maintainer decision.
Relationship to deadair
Same maintainer, separate repositories on purpose. deadair asks the environment-side question against a live cluster; detsema asks the logic-side question offline.
| deadair | detsema | |
|---|---|---|
| Ground truth | live cluster state | rule text + declared schema |
| Question | can enabled rules see telemetry, here, now? | is the logic coherent, anywhere, ever? |
| Finds | disconnected/starved rules, stale sources, lag windows | dead field references, type incoherence; next: contradictions, shadowing, semantic diffs |
| Blind to | logic defects in well-fed rules | starvation of well-formed rules |
They stay separate so detsema’s CI users and library consumers never inherit cluster clients or credential handling, and so “this library touches no network” stays verifiable per repo.
Scope and roadmap
Out of scope permanently: translating rules between dialects, hosting or ranking rule content, live telemetry monitoring, full-language SPL/KQL support (frontends cover documented subsets).
Planned: sat (schema-aware satisfiability: provably dead logic, filters that swallow their
selections), diff (semantic diff of rule changes, with witness events), shadow (corpus-wide
subsumption), fixture (evaluate IR against labeled events), KQL/EQL subset frontends,
SMT-backed regex equivalence, an MCP server over the same passes.