v0.17.0
TL;DR
RSigma v0.17.0 is the "detection-engineering toolkit" release: the rule-side reporting suite that closes the program loop, plus the daemon output-delivery layer and live daemon introspection.
- Detection-engineering reports:
rule backtestreplays an event corpus against a ruleset and diffs per-rule fire counts against declared expectations (#216);rule coveragemaps a ruleset onto MITRE ATT&CK, exports a Navigator layer, and reports coverage gaps (#221);rule visibilityturns the field-observability signal into a DeTT&CT administration pair and a visibility Navigator layer (#242);rule scorecardfuses backtest precision/recall, coverage, and fire volume into per-rule keep/tune/retire verdicts (#243). - Output delivery: detection results now flow through a per-sink async delivery layer with bounded queues, retry/backoff, batching, and an at-least-once ack-join across fan-out (#222); an OTLP output sink exports detections over OTLP/HTTP and OTLP/gRPC (#223); a generic, template-driven webhook sink delivers to Slack, Teams, Discord, PagerDuty, or any HTTP endpoint (#227).
- Daemon introspection:
engine statusqueries a running daemon from the command line (#237),engine taprecords a redactable, replayable live event fixture (#238), andengine tailstreams live detections to the terminal (#239). - Conversion reach:
backend convertresolves targets native-first and delegates anything without a native backend to an installed sigma-cli, reaching the full pySigma backend ecosystem with no new dependency (#241). rstix: Phase 2 adds STIX meta objects (#213) and relationship/sighting objects (#220), thanks to @SecurityEnthusiast; the crate is not releasable on its own yet.- Fibratus conversion fixes: emit the required
versionfield so converted rules load (#219), and mapfile_access/file_event/create_remote_threadto their idiomatic macros (#217), thanks to @rabbitstack. - Faster NATS and daemon integration tests: deterministic waits replace fixed sleeps and long-poll timeouts, cutting each suite's runtime by roughly 7x with no production code changes (#240).
- Security: bump the transitive
quinn-proto(viareqwest) to 0.11.15 to clear RUSTSEC-2026-0185, a high-severity remote memory exhaustion advisory.
rule scorecard: fuse the rule-side reports into per-rule keep/tune/retire verdicts (#243)
A new rsigma rule scorecard subcommand fuses the toolkit's existing rule-side outputs into the per-rule keep/tune/retire verdict table a detection program reviews on a cadence. It reads JSON the toolkit already emits, so it adds no new collection or evaluation: it is an offline fusion-and-verdict layer over already-aggregated reports.
- Inputs and the join. Joins the
rule backtestreport (precision proxy, recall, the corpus false-positive signal, per-rule fire counts) and therule coveragereport (per-rule ATT&CK mapping and the per-technique rule count for sole-coverage analysis), both required, into a per-rule record keyed byrule_id. Optionally enriches it with a Prometheus production-volume snapshot or/metricsendpoint (--metrics, joined byrule_titlewith colliding titles summed and flagged), a Prometheus query-API range window (--metrics-window) for last-fired, and a triage disposition feed (--triage) for the live false-positive ratio and MTTD/MTTR. Every cell records which input supplied it, and a missing optional input degrades the verdict rather than blocking it. - Verdict model. Bands default to the SOC quality-metrics thresholds and are configurable through flags and the
scorecardconfig section: retire on a precision proxy below the retire floor (0.10) or zero volume across the corpus and the metrics window (a dead rule), tune on the review band or a live false-positive ratio over the ceiling (0.50), keep on a healthy precision proxy (0.80) with enough volume and a recent fire. A retire candidate that is the sole coverage for an ATT&CK technique is downgraded to tune with a coverage-risk note, so the program never silently drops coverage. - Output and CI. Renders through the global
--output-formatlayer (table on a TTY, json/ndjson/csv/tsv) plus a--reportmarkdown or HTML program artifact grouped by verdict (extension dispatch,--report-formatoverride).--fail-on <none|tune|retire>turns it into a CI gate. Exit codes follow the house scheme:0success or under policy,1verdicts hit--fail-on,2an input is missing or unfetchable,3a bad flag or a malformed/version-mismatched report. - Config. A
scorecardconfig section follows the layered-config conventions: the verdict thresholds carry single-source defaults (pinned to the clap flags by a drift-guard test), and every input (including the two required reports,scorecard.backtest/scorecard.coverage) and the report path can be supplied from the config file. Relatedly,rule coveragenow also accepts its rule paths fromcoverage.rules. - No new dependencies. The Prometheus exposition-snapshot parser is hand-rolled (the single new untrusted-input surface, fuzzed by
fuzz_scorecard_promtext); the query-API path reuses the existingureqclient. The backtest and coverage reports deserialize through structs shared with their producers, so the consumer and producers cannot drift.
rule visibility: DeTT&CT export and a visibility Navigator layer (#242)
A new rsigma rule visibility subcommand turns the shipped field-observability signal into the two artifacts blue teams consume for data-source maturity: a DeTT&CT administration pair and a visibility ATT&CK Navigator layer. Where rule coverage reports the detection axis ("which techniques your rules detect"), rule visibility reports the data axis ("which fields and logsources you actually see"), and the two Navigator layers stack to expose data-without-detection and detection-without-data cells.
- Inputs and the join. Joins the rule logsource inventory and rule field set (from
--rules) with the observed field signal (--observed <file|->: theengine eval --observe-fieldsJSON, a savedGET /api/v1/fieldssnapshot, or stdin; or--addrfor a live daemon) through a bundled, overridable mapping table (--mapping[=<path|url>]). With no observed signal the command reports the rule-expected baseline. - Mapping table. A curated
logsource/field -> ATT&CK data source/data component/techniquetable ships in-repo so the default invocation needs no network;--mappingreads a local JSON table or fetches a URL through the same 7-day cache the lint schema download uses. Rule logsources the table does not recognize are surfaced as a hygiene list. - Scoring. Visibility rides DeTT&CT's 0-to-4 scale, derived from the fraction of a data source's mapped rule fields that were observed. A data source whose mapped fields are all unobserved is a blind spot; an observed source no rule consumes is untapped. Scores are conservative seeds marked for analyst review, with
data_qualitydimensions carrying the seed value rather than fabricated precision. - Outputs. Writes a DeTT&CT data-source administration YAML (
--dettect-data-sources), a technique-administration YAML (--dettect-techniques, visibility axis only), and a format 4.5 visibility Navigator layer (--navigator, scored 0-4). The report renders through the global--output-formatlayer (table/json/ndjson/csv/tsv). - CI signal.
--fail-on-blind-spotsexits1when a rule-expected data source has no observed telemetry. Avisibilityconfig section (mapping,fail_on_blind_spots) follows the layered-config conventions.
Reuse pySigma backends through sigma-cli delegation (#241)
rsigma backend convert now resolves targets native-first: it uses a native rsigma backend when one exists and otherwise delegates the conversion to an external sigma-cli when one is installed, so the full pySigma backend ecosystem (splunk, elasticsearch, kusto, qradar, loki, crowdstrike, and 30+ more) is reachable from the same command. It is a light subprocess wrapper with no new dependencies; no Python runtime is required unless a delegated target is actually used.
- Native-first dispatch.
postgres/postgresql/pg,lynxdb, andfibratuskeep converting natively and always win; any other target is delegated. A future native backend transparently supersedes its delegated path. - Discovery. sigma-cli is found via the
RSIGMA_SIGMA_CLIpath override or a baresigmaonPATH. When a target has no native backend and sigma-cli is absent, the command exits3with install guidance (pipx install sigma-cli,sigma plugin install <target>). - Flag mapping.
-t,-f,-p,--without-pipeline,-s, and-O key=valuepass through tosigma convertverbatim;-O correlation_method=<m>maps to sigma-cli's-c/--correlation-method. The original rule files are handed to sigma-cli, which parses, pipelines, and converts them. - Output. sigma-cli stdout is relayed through the normal output handling (stdout,
-o <file>, and the--output-format jsonenvelope). A non-zero sigma-cli exit maps to2with its stderr relayed; a missing binary or a directory--outputin delegated mode maps to3. - Listing.
backend targetsappends the installed sigma-cli targets, andbackend formats <target>shows a delegated target's formats. - Scope. CLI
backend convertonly; the MCPconverttool and thersigma_convertlibrary API convert with native backends. rsigma builtin pipeline names (ecs_windows,sysmon) are not translated; pass sigma-cli pipeline names or YAML paths in delegated mode.
Faster NATS and daemon integration tests (#240)
The nats_integration, cli_daemon_nats, and cli_daemon_dynamic suites spent most of their wall time waiting on fixed sleeps and long-poll timeouts rather than doing real work. Replacing those with deterministic waits cuts each suite's runtime by roughly 7x (30.7s to ~2.7s, 15.1s to ~4.4s, and 4.2s to ~1.9s) with no production code changes.
- Shared-consumer NATS test. The first consumer's
messages()pull stream prefetches the whole batch, so the second consumer in the shared group starved and itsrecv()blocked for the full ~30s pull-consumer expiry, which alone took the entire suite. Each receive is now bounded with a short timeout while still asserting that a consumer in the group receives a message. - NATS daemon state tests. The state-restore tests poll the SQLite state DB until the source position is persisted instead of sleeping a fixed 3s, the backward-replay test collects only the message it inspects (it was blocking the full timeout waiting for a second message a clean run never emits), and the no-output check uses an ordered canary detection rather than a fixed wait window.
- Dynamic-pipeline tests. Event ingestion,
/api/v1/reload, and/api/v1/sources/resolveare all asynchronous, so the tests now poll/api/v1/statusfor the observable counters and re-post events until the rebuilt engine takes effect, replacing the 500ms-to-3s sleeps that previously padded each step.
engine tail: stream live detections to the terminal (#239)
A new rsigma engine tail subcommand (and the GET /api/v1/detections/stream endpoint behind it) streams a running daemon's live detections, the detections-out counterpart to engine tap. Each result is the same EvaluationResult shape the sinks emit, captured after post-evaluation enrichment and regardless of which sinks are configured, so engine tail and a saved sink file are the same format.
- Server-side filters.
--level <severity>(minimum severity) and--rule <substring>(case-insensitive title/id match) are applied at the sink, so filtered-out results never cross the wire.--durationand--limitbound the stream; with neither, it streams until interrupted. - Lossy by design. Each session owns a bounded buffer and drops detections (counted) when full, so a slow tail client can never backpressure the sink task or stall the at-least-once ack-join. A dropped client connection tears the session down automatically. A trailing summary record reports
{streamed, dropped}. - Opt-in. Disabled by default; enable with
--enable-tailordaemon.tail.enabled: true. The config-file-onlydaemon.tailkeys tunebuffer_events(8192) andmax_sessions(2); the endpoint returns503when disabled,409at the session cap, and400for a badlevel. - Output. Rendered through the global
--output-formatlayer (NDJSON when piped, pretty JSON on a TTY, pluscsv/tsv/table). The client uses the synchronousureqtransport, so it builds without thedaemonfeature. - New metrics.
rsigma_tail_active_sessionsandrsigma_tail_detections_dropped_total.
engine tap: record the live event stream to a replayable fixture (#238)
A new rsigma engine tap subcommand (and the GET /api/v1/tap endpoint behind it) records a bounded window of a running daemon's live event stream as an NDJSON fixture, closing the "reproduce a missed detection locally" loop: capture what the daemon is actually seeing, optionally redact sensitive fields, then replay it against candidate rules with engine eval -e @fixture.ndjson.
- Two capture stages.
--stage decoded(the default) records what the engine evaluated (post-parse, post-event-filter), so the fixture is always valid NDJSON and replays without repeating the daemon's input flags.--stage rawrecords the input line as received, for debugging the parse/filter step. - Server-side redaction.
--redact-fields user.email,src_ipredacts named dotted paths before the data leaves the daemon; raw values never cross the wire. Redaction is deterministic per-session salted hashing (rsigma:redacted:<hex>), so equal values map to equal tokens within a capture (correlation cardinality survives replay) while the per-session salt blocks dictionary reversal and cross-fixture linkage. A non-numeric path segment meeting an array fans out to every element (fail-closed). - Bounded and lossy by design. The capture can never apply backpressure to detection: each session owns a bounded buffer and drops events (counted) when full.
--durationand--limitbound the window, a trailing summary record reports{captured, dropped, duration_ms, stage}, and a dropped client connection tears the session down automatically. The hook rides the sameArcSwapobserver pattern as--observe-fields, so the idle hot-path cost is one load per batch. - Config and limits. Opt-in: disabled by default (it exfiltrates raw events), enabled with
--enable-tapordaemon.tap.enabled: true; the endpoint returns503when disabled. The config-file-onlydaemon.tapkeys tunebuffer_events(8192),max_sessions(2), andmax_duration(5m); the endpoint returns409at the session cap and400overmax_duration. - New metrics.
rsigma_tap_sessions_total,rsigma_tap_active_sessions,rsigma_tap_events_streamed_total, andrsigma_tap_events_dropped_total. - Security. The tap exfiltrates raw events, so it inherits the admin surface's TLS/mTLS protections, ships with a kill switch for hardened deployments, and keeps redacted fields off the wire entirely. The client uses the synchronous
ureqtransport, so it builds without thedaemonfeature.
engine status: query a running daemon from the command line (#237)
A new rsigma engine status subcommand fetches a running daemon's /api/v1/status snapshot and renders it through the shared output layer, so checking a daemon no longer requires curl.
- Address resolution.
--addrtakes ahost:portor full URL and defaults todaemon.api.addrfrom the resolved config; wildcard binds (0.0.0.0,[::]) map to loopback, andhttps://URLs work for TLS deployments. It shares this convention withconfig reload. - Output. Rendered through the global
--output-formatlayer: a TTY-aware default (prettyjsonon a terminal,ndjsonwhen piped) plus aMETRIC | VALUEtableview andcsv/tsv. The snapshot covers rules loaded, events processed, detections and correlations fired, correlation state entries, uptime, and the dynamic-source summary when configured. - No daemon feature required. The command uses the synchronous
ureqclient, so a build without thedaemonfeature can still inspect a remote daemon. It exits3when the daemon is unreachable or returns an error.
Webhook output sink: deliver detections to Slack, Teams, Discord, PagerDuty, or any HTTP endpoint (#227)
A generic, template-driven webhook sink turns a detection or correlation into a templated HTTP request. It is one configurable sink rather than a set of bespoke integrations: Slack, Microsoft Teams, Discord, and PagerDuty ship as field-parametric YAML recipes in the webhooks guide, while the engine stays service-agnostic.
- Config.
--webhook <FILE_OR_DIR>(repeatable) and thedaemon.output.webhooksconfig key declarewebhooks:entries, each with anid,kind: detection | correlation, aurl, optionalheaders/bodytemplates, and optionaltimeout,retry,rate_limit,scope, andqueue_size. Validated at startup with field-scoped errors. - Templating.
url, header values, andbodyare rendered per result with the same engine as enrichers (${detection.*}/${correlation.*}plus${ENV_VAR}for secrets). The body is JSON-string-escaped so an attacker-influenced rule title or event field cannot break the payload. - Best-effort by design. Webhooks run as lossy (
on_full=drop) leaves on the async delivery layer, so a third-party endpoint never blocks the at-least-once token release for durable sinks; anything undeliverable lands in the--dlq. Connection/timeout errors,429(honoring a cappedRetry-After), and5xxretry; other4xxroute straight to the DLQ. - Rate limiting and isolation. An optional per-entry token bucket spaces requests; each webhook runs its own bounded queue and worker, so a slow webhook cannot stall other sinks.
- TLS to internal endpoints. A per-webhook
tls:block adds a custom CA bundle (for a relay served by a private CA) and a client cert/key for mutual TLS toward the endpoint. Public endpoints use the system roots with no extra config. - Observability.
rsigma_webhook_requests_total{webhook_id,outcome}andrsigma_webhook_request_duration_seconds{webhook_id}; queue depth, retries, drops, and DLQ routing read from the shared per-sink series keyed by the webhook id. - Egress and secrets. Webhooks use the daemon's egress-filtered HTTP client (
--egress-policy); keep secrets in the environment and reference them with${ENV_VAR}.
OTLP output sink: export detections over OTLP/HTTP and OTLP/gRPC (#223)
The daemon can now emit detection and correlation results to an OpenTelemetry collector, completing OTLP transport symmetry with the existing OTLP receiver.
- Two transports.
--output otlp://host:4317exports over OTLP/gRPC;--output otlphttp://host:4318exports over OTLP/HTTP (protobuf, posted to/v1/logs). Append?compression=gzipfor gzip on the wire. Both require adaemon-otlpbuild. - TLS. The
otlps://(gRPC) andotlphttps://(HTTP) schemes enable TLS, verifying the collector against the bundled public roots by default.?ca=verifies against a private CA,?client_cert=/?client_key=enable mutual TLS, and?tls_domain=overrides the verified server name. - Mapping. Each result becomes one OTLP
LogRecordunder anrsigmaresource and instrumentation scope: the Sigmalevelmaps to the OTLP severity (critical to FATAL, high to ERROR, medium to WARN, low to INFO, informational to DEBUG), the rule title is the log body, and the full serialized result is attached as structured attributes. - Delivery. The OTLP sink rides the async delivery layer: batched export with bounded retry and backoff, and terminal failures routed to the DLQ.
Async sink delivery layer: per-sink workers, retry/backoff, and isolation (#222)
Detection output now flows through a per-sink delivery layer instead of a single inline sink writer. Each --output sink runs its own bounded queue and worker task, so a slow or flaky network sink no longer immediately head-of-line blocks the others, and transient failures are retried instead of being dropped to the dead-letter queue on the first error.
- Per-sink workers. Each sink drains its own bounded queue, batches opportunistically, retries with capped exponential backoff, and routes a result to the DLQ only after exhausting retries. Fan-out across
--outputsinks is isolated up to each sink's queue depth; a slow durable sink eventually applies backpressure, the honest cost of at-least-once. - At-least-once preserved. An ack-join releases a source's acknowledgment only once every sink has committed the result (delivered or DLQ-parked), so the NATS at-least-once contract survives fan-out. Results still in a worker queue at shutdown are left unacked and redelivered on restart.
- Per-sink lossy mode. Append
?on_full=dropto an--outputsink to drop results when its queue is full instead of applying backpressure, trading durability for never stalling. The default (?on_full=block) keeps at-least-once for durable sinks. - Tunable.
--sink-retry-max,--sink-backoff-base-ms,--sink-backoff-max-ms,--sink-batch-max, and--sink-batch-flush-ms(and theirdaemon.output.*config keys) tune the shared delivery behavior; the per-sink queue depth followsbuffer_size. - New metrics.
rsigma_sink_queue_depth,rsigma_sink_retries_total,rsigma_sink_dropped_total, andrsigma_sink_delivery_failures_total, all labeled bysink. - Input-source metric parity. The HTTP (
POST /api/v1/events) and OTLP (HTTP and gRPC) push receivers now feedrsigma_input_queue_depthandrsigma_back_pressure_events_total, which previously tracked only the stdin and NATS pull sources.
rule coverage: ATT&CK Navigator export and coverage-gap analysis (#221)
A new rsigma rule coverage subcommand maps a rule set onto MITRE ATT&CK. It reads the attack.* tags off every detection and correlation rule, exports an ATT&CK Navigator layer, and reports coverage gaps against external references, the companion to rule backtest in a detection-as-code pipeline.
- Navigator export.
--navigator <FILE>writes an ATT&CK Navigator layer (format 4.5) scored by rule count per technique, the same "score function count" semantics SigmaHQ uses, so a rsigma layer overlays cleanly on the SigmaHQ baseline. Sub-technique scores are kept exact. - Cross-references.
--atomicsdiffs against the Atomic Red Team index (techniques with atomics but no rule, and rules whose technique has no atomic),--baselinediffs against a baseline Navigator layer (the SigmaHQ heatmap by default), and--targetsdiffs against a plain-text technique list. Bare--atomics/--baselinefetch their upstream defaults over HTTP with a 7-day on-disk cache and stale-cache fallback; both also accept a local path or an atomic-red-teamatomics/directory. - Sub-technique roll-up. A rule on
attack.t1059.001covers aT1059target (reported ascovered_via_subtechnique); a parent rule does not vouch for a specific sub-technique target. - Output and exit codes. The report renders through the shared output layer (
table,json,ndjson/csv/tsvper-technique rows).--fail-on-gapsexits1when any requested cross-reference reports uncovered techniques;2for unreadable rules,3for an unfetchable cross-reference input. - Config. A
coveragesection (atomics,baseline,targets,fail_on_gaps) flows throughrsigma config init/validate/show/schema, theRSIGMA_COVERAGE__*environment layer,--config, and--dry-run. - Internal. The multi-path rule loader shared with
backend convertmoved into a crate-level helper so the two commands cannot drift on rule loading.
rstix: Phase 2 — STIX relationship and sighting objects (#220)
Phase 2 adds typed STIX relationship and sighting objects (not releasable on its own until StixObject dispatch and Bundle parsing land).
model::sro:Relationship(STIX §5.1 — common properties viaSdoSroCommonPropsplusrelationship_type,source_ref,target_ref, optionaldescription,start_time,stop_time;RelSourceRef/RelTargetReftype aliases; charset andstop_timelater thanstart_timeenforced at deserialize),Sighting(STIX §5.2 — common properties plus sighting-specific fields includingdescription,first_seen,last_seen,count,summary,sighting_of_ref,ObservedDataIdforobserved_data_refs, andWhereSightedReffor identity or location inwhere_sighted_refs;SightingOfReftype alias;Sighting::COUNT_MAX; count range andlast_seen≥first_seenenforced at deserialize), and theSroObjectenum (#[non_exhaustive]). Reference-target rules forsource_ref,target_ref, andsighting_of_refare documented in rustdoc and deferred until bundle-level typed parsing.- Deserialize:
model::type_checkhoisted frommodel::metafor shared"type"validation; each SRO type rejects mismatched JSON"type"in a single serde pass (ModelError::UnexpectedObjectType); no intermediateserde_json::Valueparse. Leaf SRO andmodel::metatypes require JSON"type"— a missing"type"field is a serde error, not silently defaulted. QueryableStixObject:QueryValue::Idadded;get_fieldexposes reference fields on SRO and meta types (for examplesource_ref,sighting_of_ref,created_by_ref).- Tests:
roundtrip_strictminimal and rich fixtures undertests/fixtures/spec/sro/; negative fixtures for relationship type charset, time ordering, sighting count range,where_sighted_refstyping, cross-type"type"rejects, and missing"type"; unit coverage forSroObjectandMetaObjectQueryableStixObjectdelegation. - Docs: SRO invariant decisions in
crates/rstix/README.mdanddocs/library/rstix.md.
Fibratus conversion: emit the required version field (#219)
Fibratus rules require a top-level version attribute (the rule content version, distinct from min-engine-version); the loader rejects a rule that omits it. The converted YAML envelope never emitted it, so every converted rule failed to load. Reported by @rabbitstack.
- The envelope now emits
version:right afteridfor both detection and correlation rules, regardless ofemit_metadata. It defaults to1.0.0and is overridable with-O version=<value>.
Fibratus conversion: file and remote-thread macro fixes (#217)
Three Fibratus conversion bugs reported by @rabbitstack are fixed, so the converted rules now use the idiomatic macros the upstream loader expects instead of raw or unmapped predicates.
file_accessrules now map to theopen_filemacro. Thefibratus_windowspipeline had nofile_accesshandler, so file open rules (Microsoft-Windows-Kernel-File ETW provider) emitted the raw Sigma fieldsFileName/Imagewith no event scope, which the loader rejects. The pipeline now renamesFileName -> file.pathandImage -> ps.exeand injects theopen_filediscriminator triple (evt.name = 'CreateFile' and file.operation = 'OPEN' and file.status = 'Success').file_eventrules now collapse to thecreate_filemacro. The disposition guard was appended after the rule body and lacked the success-status clause, so the run never matched the macro and left a rawevt.name = 'CreateFile' ... and not (file.operation ~= 'OPEN')body. The fullcreate_filetriple is now injected contiguously and in macro order.create_remote_threadrules now use thecreate_remote_threadmacro instead of degrading to the barecreate_thread. The pipeline injects the cross-process guards (evt.pid != 4,evt.pid != thread.pid) the macro requires.- Recognizer. The macro recognizer now accepts the De Morgan negated-equality form (
not (field ~= 'x')) as equivalent to a macro'sfield != 'x'clause, so disposition and cross-process guards injected through the pipeline fold back into their macros.use_macros=falsestill emits the raw expansion. - Pipelines. The
add_conditiontransformation gained an optionalfield_refsmap that injects field-to-field comparisons (lowered through thefieldrefmodifier) rather than literals.
rule backtest: corpus replay with per-rule expectations (#216)
A new rsigma rule backtest subcommand replays an event corpus against a ruleset and diffs the per-rule fire counts against declared expectations, the per-rule fixture harness that engine eval --fail-on-detection could not provide (that check is corpus-global and passes when any rule fires).
- Corpus replay.
--corpustakes a file or a directory walked recursively, with extension dispatch (.ndjson/.jsonlas NDJSON,.evtxvia the evtx feature, everything else through--input-format). Correlation state is reset per corpus file so each file is an independent time slice. - Expectations. An optional
--expectationsYAML asserts per rule (by id or title):at_least,at_most, orexactly, optionally scoped to one corpus file. A rule that fires with no covering expectation is surfaced as a potential false positive, governed by--unexpected(fail/warn/ignore). - Reports. The report renders through the shared output layer (
table,json,ndjson/csv/tsvper-rule rows) and can be written to--report(JSON) and--junit(a hand-rolled JUnit XML, no new dependency). It carries per-rule fires, a per-corpus-file breakdown, the unexpected-fire set, and a per-logsource false-positive-density rollup. - Exit codes follow the house scheme:
0all expectations met,1a failed expectation or a policy-failing unexpected fire,2unreadable rules,3a bad expectations file or corpus path. - Config. A
backtestsection (rules,corpus,expectations,unexpected,pipelines, and the syslog input knobs) flows throughrsigma config init/validate/show/schema, theRSIGMA_BACKTEST__*environment layer,--config, and--dry-run. - Internal. The format-aware eval stream loop moved into a shared
commands::eval_streammodule soengine evalandrule backtestcannot drift on input parsing; eval behavior is unchanged.
rstix: Phase 2 — STIX meta objects (#213)
Phase 2 adds STIX meta objects (not releasable on its own until StixObject dispatch and Bundle parsing land).
model::meta:MarkingDefinition(STIX §7.2.1 optional common properties —created_by_ref,external_references,object_marking_refs,granular_markings; legacy TLP 1.x and current TLP 2.0 encodings;IS_NON_VERSIONABLE/is_non_versionable(); nine TLP UUID constants),ExtensionDefinition(created_by_refrequired per §7.2.2),LanguageContent(contentsas nestedBTreeMapfor stable JSON key order), and theMetaObjectenum (#[non_exhaustive]).- Deserialize: each meta type validates JSON
"type"againstTYPE_NAMEin a single serde pass (ModelError::UnexpectedObjectType); no intermediateserde_json::Valueparse. - Tests:
roundtrip_strictfor complete types (meta objects,ExternalReference,GranularMarking,ExtensionMap); subsetroundtripforSdoSroCommonProps/ScoCommonPropsfixtures that carry unmodeled SDO keys. Fixtures undertests/fixtures/spec/meta/include minimal TLP markings, a rich marking-def with common properties, and cross-type reject coverage. Unit pins for all nine TLP ids. - Docs: STIX object model version vs TLP marking encoding in
crates/rstix/README.mdanddocs/library/rstix.md.
Security: transitive quinn-proto bump for RUSTSEC-2026-0185
cargo audit flagged RUSTSEC-2026-0185, a high-severity (7.5) remote memory exhaustion in quinn-proto from unbounded out-of-order stream reassembly, published 2026-06-22. It reaches the tree transitively through reqwest -> quinn -> quinn-proto. A targeted cargo update -p quinn-proto --precise 0.11.15 moves the workspace and fuzz lockfiles from 0.11.14 to the fixed 0.11.15 with no other dependency changes.