Skip to content

v0.19.0

Choose a tag to compare

@mostafa mostafa released this 13 Jul 08:18
f90c4f4

TL;DR
RSigma v0.19.0 is the "secured daemon control plane and shipped threat-intel library" release: the daemon API gains authentication, granular RBAC, and an audit trail, detection authoring grows rule drafting and schema discovery, rstix completes its validation pipeline and pattern engine and ships to crates.io, and the documentation moves to a new docmd site on rsigma.io.

  • Daemon API security and governance: opt-in bearer-token authentication with resource:action RBAC and anonymous permission grants (#304), plus an append-only control-plane audit trail persisted in the state database (#307).
  • Detection authoring: draft a complete Sigma rule from exemplar events (#286), mine ranked candidate schema signatures from unknown events (#285), and schema and logsource routing v2 with richer signatures and schema-derived logsource pruning (#277).
  • rstix threat-intel library: STIX pattern evaluation, canonical printer, and Indicator wiring (#276, #296); a validation pipeline taken from scaffold to all twelve checks to a conformance corpus (#297, #298, #315); and the crate now publishes to crates.io (#316), thanks to @SecurityEnthusiast.
  • WASM: an ABI version 1 contract and a JavaScript-free wasm32-unknown-unknown build for rsigma-parser and rsigma-eval (#306).
  • Dynamic sources: http sources can send a request body (#310), copy-paste disposition source recipes for TheHive, Jira, and GitHub Issues (#311), and pipeline-embedded sources: blocks are removed in favor of standalone source files (#293).
  • Integrations: the MCP convert_rules tool can delegate to sigma-cli for the pySigma backend set (#290).
  • Documentation: migrated to docmd with search on rsigma.io and refreshed branding (#312, #313, #314).
  • Cleanup and performance: the deprecated flat CLI aliases are removed (#292), and the benchmark suite is refreshed with four new suites (#291).
  • Fixes and dependencies: de-flaked the Windows schema-observer and macOS TLS end-to-end tests (#305, #295), and two rolled-up dependency bumps (#289, #308).

Publish rstix to crates.io (#316)

The rstix STIX 2.1 library now ships as part of the release. publish.yml publishes it to crates.io alongside the other workspace crates (it has no workspace dependencies, so it needs no index-wait), docs.rs builds it with all features so the pattern and validate surfaces are documented, and the weekly fuzz workflow schedules the fuzz_stix_pattern target next to the existing rstix parse and validate fuzzers. The crate README and the library docs page are refreshed for the crates.io landing page and link to docs.rs/rstix.

rstix Validation Pipeline: conformance corpus and diagnostic coverage (validate feature) (#315)

Closes Validation Pipeline conformance work for phases 1–4:

  • Per-code coveragetests/validate_diagnostic_coverage.rs asserts one integration case per DiagnosticCode::ALL entry (39 codes).
  • Conformance corpusvalidate_conformance.rs gates conformance/ plus validation/bundle-*.json negatives under interop_strict.
  • Parse bridge — all ParseError variants and mapped ModelError messages emit structured diagnostics; model invariants use tagged serde payloads (model/serde_error.rs) so validation never downgrades to STIX-E0001 (reserved for JSON syntax/EOF).
  • Wire-format validators — lightweight checks at parse; strict IDNA / RFC 5322 / WHATWG checks gated behind validate (optional idna, email_address, url deps) as pipeline STIX-I0002 findings.
  • Model mapping — exhaustive ModelErrorSTIX-E/W/I/H code mapping in validate/model_mapping.rs.

Docmd site branding (#314)

The docmd plugin copies assets/rsigma-logo.png into the published site at build time, trims transparent padding with sharp, and writes a square favicon PNG. The sidebar shows the trimmed mark with an RSigma text label styled for light and dark themes.

Docs site search on rsigma.io (#313)

Build the docmd site with base / and canonical URL https://rsigma.io/ so the search client loads search-index.json from the site root. The previous /rsigma/ base matched the old GitHub Pages project path but broke search after the custom domain went live.

Documentation site migrated to docmd (#312)

Replaces MkDocs Material with docmd for the published docs at https://timescale.github.io/rsigma/. The whole docmd project is self-contained under docs/ (config, package.json, local plugin, assets, and Markdown under docs/content/). docs/docmd.config.js carries the reorganized navigation (User Guide grouped into Author/Test, Deploy/Detect, Alert/Respond, Measure/Hunt, Operate, and Integrate sub-categories; Benchmarks under Reference; Editors/Ecosystem under Integrations; Release Notes/Contributing/Security under Project) and defaults to dark mode. A local docmd-plugin-rsigma plugin preserves Cargo.toml-synced version, MSRV, and lint-count macros and the inlining of root CHANGELOG/CONTRIBUTING/BENCHMARKS/SECURITY, and strips docmd's site-root <base> tag so the project subpath resolves correctly. MkDocs-specific syntax (!!! admonitions, Material grid cards) is converted to docmd callouts and card grids. CI builds from docs/ with npm run docs:build and npm run docs:validate via SHA-pinned first-party GitHub Actions.

Disposition source recipes (docs) (#311)

A new Disposition Source Recipes guide with copy-paste --disposition-source configs that pull analyst verdicts from TheHive, Jira, and GitHub Issues into the triage feedback loop. Each recipe is one HTTP dynamic source with a jq extract that reshapes the case system's API response into disposition records, plus its verdict mapping, ${ENV_VAR} auth, and the identity round-trip and idempotency reasoning. The three sources files are committed as test fixtures and their extracts run against canned API responses in CI, so the documented recipes cannot silently drift from what the ingest path accepts. Docs-only beyond the fixture test; no engine or daemon change.

Request body for http dynamic sources (#310)

The http dynamic-source type gains an optional body field, sent verbatim after ${VAR} environment expansion, so a source can poll a query API that requires a request body (GraphQL, an Elasticsearch/OpenSearch _search, TheHive 5's /api/v1/query). A source with a body and no explicit method defaults to POST; an explicit method still wins. Content-Type is not inferred and should be set in headers. The reference documentation for headers is also corrected: ${VAR} references have always been expanded from the environment at fetch time, which the field table previously said was unimplemented.

Dependency bumps (#308)

Rolls up five open Dependabot PRs into a single merge. Rust (workspace Cargo.lock): tower-http 0.6.11 to 0.7.0 (#299), cel 0.13.0 to 0.14.0 (#300), rmcp 1.8.0 to 2.1.0 (#301), and phf 0.13.1 to 0.14.0 (#303); rsigma-mcp is migrated to the rmcp 2.x API (Resource, ContentBlock). CI (all repinned by commit SHA, batched via the actions-updates group, #302): taiki-e/install-action v2.82.4 to v2.82.7, docker/setup-buildx-action v4.1.0 to v4.2.0, docker/login-action v4.2.0 to v4.3.0, docker/build-push-action v7.2.0 to v7.3.0, github/codeql-action/upload-sarif v4.36.2 to v4.36.3, docker/metadata-action v6.1.0 to v6.2.0, and actions/attest-build-provenance v4.1.0 to v4.1.1. The rusqlite 0.39 to 0.40.1 bump (#234) stays held back on MSRV 1.88.

Control-plane API audit trail (#307)

Adds an append-only audit log for control-plane mutating daemon API calls (who, what, when, outcome), persisted in the existing SQLite state database when --state-db is configured. Auto-enabled with a state database; optional daemon.api.audit config tunes retention, optional sink emission, or disables the trail. Each record stores method, matched route pattern, token name, HTTP status, timestamp, and a SHA-256 hex digest of the request body (never the body itself). Data-plane ingest and OTLP are excluded. GET /api/v1/audit (audit:read) serves paginated entries; bodies over max_body_bytes (default 64 KiB) get 413 and the rejected attempt is recorded. New metrics: rsigma_audit_records_total, rsigma_audit_write_errors_total.

WASM ABI contract and build compatibility (#306)

Documents ABI version 1 for future direct wasm32-unknown-unknown hosts, including the linear-memory ownership model, packed status/result values, result descriptors, stable JSON error envelopes, and compatibility rules. CI builds rsigma-parser and rsigma-eval for wasm32-unknown-unknown with default features disabled, then instantiates a module linking them in a JavaScript-free runtime (Wasmtime) to prove it runs and carries no JavaScript imports.

  • rsigma-parser gains a default-on fix feature around the source-preserving yamlpath/yamlpatch auto-fix implementation. Parsing, validation, lint diagnostics, and fix metadata remain available without it.
  • rsigma-eval disables rsigma-parser default features because evaluation does not use the auto-fix implementation, uses compile-time AHash seeding only on wasm32-unknown-unknown, and drops chrono's wasmbind feature on that target so the module stays host-neutral (no wasm-bindgen/js-sys imports). Native targets retain runtime-randomized hashing and the default chrono behavior.
  • The first-party rsigma-wasm guest crate and published .wasm artifact do not ship in this change.

De-flaked the daemon schema-observer E2E test on Windows (#305)

SchemaObserver::observe bumped its events_observed counter before recording the classification result, so the /api/v1/schemas snapshot (served on a different thread than event ingestion) could report events_observed == N while the Nth event's classified/unknown increment had not yet landed. A reader that waited on events_observed then read a torn snapshot with unknown short by one, which surfaced as a Windows CI failure in schemas_endpoint_reports_per_schema_and_unknown_counts. events_observed is now derived as classified + unknown inside the snapshot, so every snapshot is internally consistent regardless of thread interleaving.

Daemon API authentication (bearer tokens + granular RBAC) (#304)

Adds opt-in bearer-token authentication with resource:action permissions to the daemon API. Off by default: without configuration the routes stay open as before, and GET /healthz / GET /readyz are always unauthenticated so liveness probes never need secrets.

  • Two ways to enable. --api-token-env <ENV_VAR> names an environment variable holding a single full-admin token (the secret never appears on the command line or in YAML), or the daemon.api.auth config block declares named roles and per-token role assignment. The flag and the block are mutually exclusive.
  • Granular RBAC. Every route maps to a resource:action permission (silences:write, reload:execute, tap:read, events:ingest, ...); the mapping fails closed, so an unmapped route requires the full * grant. Roles are permission sets with * wildcards: built-in reader (*:read), operator (*:read plus control-plane writes except reload), ingest (events:ingest only, so a log shipper's token cannot create silences), and admin (*), plus operator-defined roles (or inline per-token permissions).
  • Anonymous permissions. anonymous_permissions grants a permission set to requests without an Authorization header: ["metrics:read"] keeps Prometheus scraping token-free, ["*:read"] protects only the mutating endpoints. A presented-but-unrecognized token is always rejected, never downgraded to the anonymous grants.
  • Secret posture. Each token's token_env names an environment variable resolved once at startup (the webhook secret_env posture); a missing or empty variable, a duplicate token name or secret, an unknown or redefined built-in role, or a malformed permission string fails startup with a clear message. Comparison is constant-time per candidate token.
  • Failure semantics. Missing or invalid credentials get 401 with WWW-Authenticate: Bearer; a recognized token without the required permission gets 403 naming the missing permission. OTLP/gRPC clients authenticate with the same authorization metadata and receive UNAUTHENTICATED/PERMISSION_DENIED status codes. Rejections increment the new rsigma_api_auth_failures_total{reason} counter and log at warn with the token name, never the secret. The established identity is attached to the request for handlers to attribute the call.
  • Docs. New Authentication sections in the HTTP API reference (per-endpoint permission table), the security reference, and the engine daemon CLI page; the config template and configuration reference cover the daemon.api.auth block.

rstix Validation Pipeline: all twelve checks (validate feature) (#298)

Implements the full validation check set behind validate:

  • All twelve checks — schema, ID structure, property types, open vocabulary, pattern parse/semantic, references, cross-object semantics, extension resolution, and TLP marking computation are wired through the dispatcher (no STIX-I0020 stubs).
  • Shared helpersmodel_bridge, object_validate, semantic, and wire modules map ModelError / wire JSON to pipeline diagnostics; overlapping Bundle::validate() findings migrate to STIX-E/W/I codes per DD-VP-001.
  • Pattern splitPattern::parse_ast and Pattern::type_check_ast expose parse-only vs type-check phases for STIX-E0010 / STIX-E0011.
  • Integration tests — validation fixtures assert STIX-W0031, STIX-E0024, STIX-I0002, and STIX-W0010 through Validator::consumer_strict().
  • Conformance harness hardening — conformance tests are locked to bundled in-repo fixtures for deterministic CI (no external env override path).

rstix Validation Pipeline scaffold (validate feature) (#297)

Adds the profile-based Validation Pipeline module behind the optional validate feature (implies serde + pattern):

  • Validator / ValidatorBuilder — four named profiles (consumer_permissive, consumer_strict, producer_strict, interop_strict) and custom check selection.
  • Diagnostic / DiagnosticCode / Severity — structured STIX-E/W/I/H taxonomy with ValidationReport::is_valid() (no Error-severity diagnostics).
  • Raw JSON entryvalidate_json_str / validate_json_value emit STIX-E0001 on malformed JSON (line/column span) instead of panicking or failing only at deserialize.
  • Check dispatcher — all twelve ValidationPhase variants wired; remaining check implementations follow in a later release.
  • LeniencyValidationReport::is_valid() respects profile policy (Standard vs Zero for interop); STIX-H0001 hint taxonomy wired.
  • Type discrimination scaffold — non-bundle JSON roots emit STIX-E0002 with property_path / fix_suggestion; ValidatorBuilder::with_allow_custom and with_parse_options expose parse policy.
  • Stub visibility — not-yet-implemented checks emit informational STIX-I0020; profile rustdoc and [Validator::implemented_phases] document current coverage.
  • DD-VP-001 — documents the boundary between advisory Bundle::validate() (model::ValidationReport) and validate::Validator.
  • fuzz_rstix_validate_json — libFuzzer target over Validator::validate_json_str; seeds in fuzz/seeds/fuzz_rstix_validate_json/.

rstix Pattern Engine: canonical printer, Indicator wiring, and pattern semantics (#296)

Adds the remaining pattern feature pieces for STIX indicator patterns and closes §9.6.1 evaluation semantics:

  • Pattern::canonical / Display — AST → canonical STIX pattern string; parse → print → parse preserves semantics (§9.8 fixture round-trips).
  • IndicatorPattern::Stix { parsed } — STIX indicators deserialize with Pattern::parse(raw) when pattern is enabled; invalid patterns fail at deserialize time.
  • IndicatorPattern::evaluate / evaluate_observed_data — delegate to the parsed pattern for STIX indicators; NonStixPattern for YARA/Snort/etc.
  • IndicatorBuilder — fluent programmatic construction of indicators (stix_pattern, external_pattern, valid_from, kill-chain phases); STIX patterns parse and type-check at build() when pattern is enabled; runs [Indicator::validate]. Design decision DD-PE-001 documents why validation runs at build() rather than in setters.
  • fuzz_stix_pattern — libFuzzer target over parse + canonical print; seeds in fuzz/seeds/fuzz_stix_pattern/ (§9.8 fixture lines).
  • LIKE / MATCHES NFC normalization — pattern constants and string property haystacks NFC-normalized before comparison.
  • MATCHES PCRE DOTALL — regex compilation enables . across newlines per §9.6.1.
  • evaluate() with at: None — non-temporal patterns accept observations without timestamps; temporal patterns still return MissingTimestamp.
  • Absent optional _ref properties — comparisons do not match; EXISTS is false; dangling or non-SCO targets still return RefResolution.

De-flaked the TLS misconfiguration E2E tests on macOS (#295)

spawn_expect_failure in the CLI test harness raced the daemon's exit against its stderr: the collection loop broke as soon as try_wait() saw the process gone, so when a misconfigured daemon failed fast (as encrypted_key_password_is_rejected_with_guidance does, the encrypted-key check being the first thing TLS init runs), the reader thread could still be holding the error line and the test asserted against empty stderr. The helper now drains the channel after reaping the child; closing the pipe ends the reader thread, so the drain terminates deterministically.

Removed pipeline-embedded sources: blocks (#293)

Dynamic source declarations no longer live inside pipeline files. A pipeline that still declares an inline sources: block is now rejected with a hard parse error that points at rsigma rule migrate-sources; source declarations come exclusively from standalone --source files, and a pipeline only references them with ${source.<id>}. This completes the deprecation cycle started in v0.12.0 (#135, visible-deprecated) and continued in v0.13.0 (#136, hidden from docs).

  • Library API. rsigma_eval::Pipeline drops its sources field; Pipeline::is_dynamic() is now driven purely by ${source.*} references, and validate_source_refs no longer takes a pipeline-local declaration set. parse_sources is now exported for tooling that reads a raw sources: block. The runtime RuntimeEngine gains set_external_sources, resolving and expanding references against the external declarations (carried across hot-reload), and expand_includes takes the external sources for its remote-include check.
  • Reference detection fix. List-valued pipeline vars (the common value_placeholders shape, e.g. malicious_commands: ["${source.cmd_list}"]) are now correctly recognized as dynamic source references; previously only scalar var values were scanned, which the removed inline sources: block had masked.
  • rule migrate-sources reads the inline sources: block directly (rather than through the now-rejecting pipeline parser) so it keeps working as the migration path.
  • Docs and tests move to the external-only model throughout; the runtime pipeline_deprecation module and its stderr warning are gone.

Removed the deprecated flat CLI aliases (#292)

The twelve flat top-level subcommands (eval, daemon, parse, validate, lint, fields, condition, stdin, convert, list-targets, list-formats, resolve) are removed. They shipped as visible-deprecated forwarders in v0.12.0 (#124), were hidden from rsigma --help in v0.13.0 (#125), and reach end-of-life here. Invoking a removed alias now fails with clap's unrecognized subcommand error and lists the available command groups. Use the noun-led groups instead: engine eval, engine daemon, rule parse, rule validate, rule lint, rule fields, rule condition, rule stdin, backend convert, backend targets, backend formats, and pipeline resolve. The per-alias forwarding dispatch and the stderr deprecation warning are gone; the group enums remain the single source of truth for every argument.

Benchmark refresh and two new suites (#291)

Reran every benchmark suite on current main (Apple M4 Pro, 2026-07-05) and rewrote BENCHMARKS.md from the results, replacing the 0.9.0-era figures and their freshness disclaimer. The refreshed doc now also covers suites that existed but were never documented: the bloom prefilter rejection sweep, logsource pruning, and result serialization.

  • New schema bench (rsigma-eval) measures per-event SchemaClassifier::classify cost against the built-in signature set (early match, mid-list match, full-scan unknown, and the ambiguity-aware variant): 216-548 ns per event, so --schema-routing and --observe-schemas are effectively free at pipeline throughputs.
  • New enrichment bench (rsigma-runtime) measures the CPU-only floor of the post-evaluation enrichment pipeline with the template primitive over 1,000-result batches at one and four enrichers (~0.6-0.9 us per result per enricher).
  • New array bench (rsigma-eval) measures the sigma-version: 3 array-matching paths against a flat-field baseline: implicit any-member matching, [any]/[all] object scopes at varying lengths and match positions, and positional indexing. Cost is linear in member count (~35-60 ns/member non-firing, ~110-250 ns/member firing, since the fan-out collects every matching member); positional indexing is O(1).
  • New routing bench (rsigma-eval) measures end-to-end --schema-routing dispatch (classify, route, evaluate on the per-schema engine) over a mixed ECS/Sysmon/unknown stream against a single unrouted engine, separating the sub-microsecond dispatch cost from the real matching work the pipeline-mapped engines do.
  • New input_formats bench (rsigma-runtime, --features logfmt,cef,evtx) completes the format matrix: logfmt (631K events/s), CEF (527K events/s) through the LogProcessor pipeline, and EvtxFileReader binary parsing over the security.evtx fixture (195K records/s).
  • New otlp bench (rsigma-runtime, --features otlp) measures logs_request_to_raw_events, the OTLP ingest-side flattening of an ExportLogsServiceRequest into engine events: a flat ~2.3 us per record independent of batch size.
  • New runtime_observe_fields group in the runtime_throughput bench measures the --observe-fields hot-path overhead: ~0.3 us per event on seven-key JSON events.
  • Fixed the dynamic_pipelines bench, which panicked since load_rules gained fail-closed dynamic-source re-resolution: the engine-build and reload benchmarks now run inside the tokio runtime context they require.

MCP sigma-cli delegation: reach the pySigma backends from convert_rules (#290)

Extends the native-first sigma-cli delegation that rsigma backend convert gained in #241 to the MCP server: when rsigma mcp serve runs with the new --allow-sigma-cli flag (config key mcp.allow_sigma_cli), the convert_rules tool delegates any target without a native backend to an installed sigma-cli, so an agent can convert to splunk, elasticsearch, kusto, qradar, loki, and the rest of the pySigma backend set. The rsigma_convert library API stays native-only by design.

  • Off by default. Delegation spawns a subprocess, a category change from the server's pure in-process posture, so it is opt-in for both stdio and HTTP. With delegation off, an unknown target keeps returning the structured error, now extended with a hint that --allow-sigma-cli unlocks the delegated targets.
  • Hardened. Delegated path and file-based pipelines inputs are canonicalized and confined to --rules-dir when one is configured (a path that escapes it is refused), inline yaml is staged through a private temporary file, the subprocess is spawned with kill_on_drop under a 60-second timeout, and at most two delegations run concurrently.
  • Envelope. A delegated result carries engine: "sigma-cli", the per-line queries split the CLI's JSON envelope uses, a verbatim raw field (the faithful copy for multi-line output formats such as Loki ruler), and warnings with sigma-cli's zero-exit stderr. A missing sigma-cli returns ok: false with install guidance.
  • Discovery. list_backends appends the installed sigma-cli targets (flagged engine: "sigma-cli", shadowed native names excluded) when delegation is enabled, mirroring backend targets on the CLI; native entries gain a matching engine: "native" flag.
  • Shared helper (public API changes). The delegation helper moved from rsigma-cli into a new feature-gated rsigma_convert::sigma_cli module (std-only, no new dependencies) with a classify_output outcome classifier, so the CLI and the MCP server share one flag mapping and cannot drift; CLI behavior is unchanged. RsigmaMcp::new gains a third allow_sigma_cli: bool parameter.

Dependency bumps (#289)

Rolls up the open Dependabot PRs into a single merge, regenerating the lockfiles against current main rather than replaying stale lockfile bases, then refreshes everything else cargo update, npm update, and the docs pins had pending. Rust (workspace Cargo.lock): similar 2.7.0 to 3.1.1 (#282, a major bump also reflected in crates/rsigma-cli/Cargo.toml), bytes 1.11.1 to 1.12.0 (#283), yamlpath 1.25.2 to 1.26.1 (#284), cmov 0.5.3 to 0.5.4 (#288), and the patch-updates group (#281) log 0.4.33, uuid 1.23.4, rustls 0.23.41, env_logger 0.11.11 plus time and jsonschema; a follow-up full cargo update regenerates both the workspace and fuzz/Cargo.lock to the latest MSRV-compatible set, notably aws-lc-rs 1.17.1, bitflags 2.13.0, dashmap 6.2.1, env_filter 2.0.0, jiff 0.2.31, jsonschema 0.46.9, rmcp 1.8.0, shlex 2.0.1, time 0.3.53, tree-sitter 0.26.10, zerocopy 0.8.52, and the zizmor crate family (subfeature, tree-sitter-iter, yamlpatch, yamlpath) 1.26.1. CI (all repinned by commit SHA, batched via the actions-updates group, #280): actions/cache v5.0.5 to v6.1.0, actions/setup-python v6.2.0 to v6.3.0, taiki-e/install-action v2.82.0 to v2.82.4, and zizmorcore/zizmor-action v0.5.6 to v0.5.7. VS Code extension: vscode-languageclient 10.0.0 to 10.0.1 (#278) and @types/node 25.9.3 to 26.0.1 (#279), then an npm update pass brings the lockfile to vscode-languageclient 10.1.0 and @types/node 26.1.0; tsconfig.json gains an explicit "types": ["node"] because TypeScript 6 no longer resolves the Node globals implicitly with this configuration. Docs build (docs/requirements.txt): pymdown-extensions 11.0.1, mkdocs-section-index 0.3.12, mkdocs-git-revision-date-localized-plugin 1.5.3, mkdocs-rss-plugin 1.19.0, mkdocs-llmstxt 0.5.0, and mkdocs-redirects 1.2.3, verified with mkdocs build --strict. The rusqlite 0.39 to 0.40.1 bump (#234) stays held back: it still pulls libsqlite3-sys 0.38.1, whose build script needs the cfg_select! macro that is unavailable on the pinned MSRV (1.88.0).

Rule drafting from logs (#286)

Turns exemplar events into a complete draft Sigma rule, the detection-authoring sibling of schema signature discovery: feed the malicious or noteworthy events (optionally contrasted against a baseline corpus of normal traffic) and get back paste-ready standard Sigma YAML to review, edit, and commit. The tool proposes, a human decides; metadata stays as explicit TODO placeholders.

  • Drafting core — a new rsigma_eval::rule_draft module profiles every field across the exemplars, drops volatile fields (timestamp-shaped names and values, UUID/GUID shapes, per-event counters, high-entropy unique values), scores the survivors by value stability times baseline rarity, infers a value form and modifier per field (plain equals, OR value list, endswith/startswith from a shared path tail/prefix, contains/contains|all from shared tokens with a minimum token length and baseline-generic rejection), escapes literal Sigma wildcards in observed values, splits exemplar variants into selection_* groups with 1 of selection_* when the split is earned, and infers the logsource from the built-in schema classifier (a shared Sysmon EventID maps to its Sigma category). The core is pure and deterministic: the rule id is caller-supplied and repeated runs are byte-identical.
  • Verified before emitted — the draft is parsed and compiled through the real evaluation engine and must match every exemplar (fields that break the match are dropped, bounded by a minimum-field floor; below it the command errors instead of emitting an over-broad rule), the lint catalogue runs over the YAML with findings surfaced as warnings, and the baseline hit count and rate are reported as the estimated false-positive rate.
  • rule draft — the offline command: exemplars via inline JSON, @file NDJSON, @file.evtx (with the evtx feature), or stdin, plus --baseline @file. Flags: --max-fields, --min-prevalence, --include-field/--exclude-field, --logsource-category/--logsource-product/--logsource-service, --title, --skip-baseline-eval, and --emit yaml|report (default yaml prints the rule with the field report on stderr; report renders the full analysis through the global output formats). The UUIDv4 id is generated at the CLI layer.
  • Docs — a new rule draft CLI page and a Drafting Rules from Logs guide, including the schema-native note: the draft uses the exemplars' native field names, so evaluate it without a mapping pipeline.

Schema signature discovery (#285)

Turns the unknown-schema signal the schema tooling surfaces into ranked candidate declarative signatures, so operators stop hand-writing every signature from scratch. Pure-Rust, glass-box mining (clustering plus discriminative feature selection); the output is the same schemas: YAML the classifier already consumes, so every proposed predicate is human-readable and reviewable. Additive and opt-in throughout; no black-box model, and nothing is applied automatically.

  • Mining core — a new rsigma_eval::schema_discovery module clusters unrecognized events by field-key shape, selects the fields (and low-cardinality, non-sensitive values) that discriminate each cluster with a value-based diversity guard and cardinality-weighted scoring, validates proposals against the built-ins, and renders a paste-ready schemas: block that round-trips through parse_schema_signatures.
  • engine discover-schemas — an offline command that mines a JSON/NDJSON corpus (excluding events an existing built-in or --schema-config signature already recognizes) and prints ranked candidates plus the YAML. Flags: --schema-config, --min-support, --similarity, --max-candidates, --max-predicates, --no-value-markers, --emit yaml|report, and --dry-run (reclassify the corpus with the proposals loaded and report the before/after per-schema counts).
  • Live daemon surface — a new --discover-schemas flag (implies --observe-schemas) enables a separate, redacted, keys-only sampler of unrecognized events, and GET /api/v1/schemas/suggestions mines it into presence-only candidate signatures. A new rsigma_unknown_schema_clusters gauge tracks how many distinct schemas discovery would propose, refreshed cheaply via a clustering-only count so /metrics and GET /api/v1/schemas never re-run the full mining pipeline. DELETE /api/v1/schemas resets the observer and refreshes the capped discovery sample without a restart. The shipped unknown_shapes semantics are unchanged.
  • Docs — a new engine discover-schemas CLI page and schema-routing guide section, plus updates to the daemon CLI, HTTP API, and metrics references.

Docs

  • Added Rustinel, an open-source cross-platform endpoint detection engine, to the Built with RSigma section on the docs home page. Rustinel ships RSigma as an opt-in Sigma backend alongside its built-in matcher.

Schema and logsource routing v2 (#277)

Extends the shipped schema routing and logsource-aware evaluation with richer signatures, schema-derived logsource correctness, authoring tooling, and hardening. All additive and opt-in; existing schema configs and --logsource-* invocations behave identically.

  • Schema-derived logsource pruning — a recognized schema now supplies an event's logsource for conflict-based pruning even when the event carries no explicit product/service/category field, so a Sysmon-classified event prunes Cisco/Linux rules instead of false-positive matching on a mapped field. Built-in implied logsources for the platform-locked sysmon, windows_eventlog, ecs_windows, and ecs_linux schemas, overridable per binding with a logsource: block. Resolved per event in SchemaRouter (explicit fields, then the schema's implied logsource) and fed to a new conflict-based Engine::evaluate_pruned.
  • ECS platform specializations and schema aliases — built-in ecs_windows/ecs_linux signatures recognize ECS events carrying a platform marker and carry the platform for pruning, while aliasing to ecs so existing ecs bindings still match them. A general routing.aliases map lets an event classified as one schema route as another, so one binding covers a family of related schemas.
  • Richer signature predicates — numeric comparisons (gt/gte/lt/lte), set membership (in), cross-field equality (field_equals_field), and recursive boolean groups (not/any/all) so a signature can express OR/NOT and value ranges, not only AND of string/presence forms.
  • Custom logsource dimensionslogsource_compatible and LogSourceExtractor handle arbitrary LogSource.custom dimensions; the --logsource-field-map / --event-logsource flags and config block accept custom.<name>=... entries.
  • engine classify tooling--explain shows per-predicate pass/fail for the matched signature (or the closest near-miss for an unknown event), --check statically validates a schema config (unreachable signatures, unknown or duplicate bindings, missing pipeline files) and exits non-zero on findings, and a routing section triggers a per-event routing dry-run.
  • Hardening and visibility — ambiguous classifications (two different-name signatures tied at the winning specificity) are surfaced in engine classify and the rsigma_events_ambiguous_schema_total counter; the schema observer samples bounded, redacted field-key shapes of unknown events; GET /api/v1/schemas gains unknown_shapes and a per-schema routing_pruning summary; and new rsigma_schema_rules_eligible{schema} / rsigma_schema_rules_pruned{schema} gauges plus an engine eval end-of-run summary report per-schema pruning.
  • Per-schema rule partitioning (gated, opt-in)--schema-partition-rules (or schema.partition_rules) compiles each platform-locked per-schema engine with only the rules whose product can apply, cutting the N-copies memory cost. Conservative and safe by construction: the default set and any set reachable by a cross-platform schema or whose pipelines rewrite product keep the full ruleset. Off by default; validate against your corpus before enabling.
  • Docs — a new Schema Signatures reference enumerating every predicate form and its semantics, plus updates across the schema-routing and logsource-routing guides, the classify page, and the configuration, metrics, HTTP API, and library references.

rstix Pattern Engine: evaluation (Levels 1–3) (#276)

Adds STIX pattern evaluation to the pattern feature:

  • Pattern::evaluate — match a parsed pattern against timestamped observations (Levels 2–3: AND, OR, FOLLOWEDBY, WITHIN, REPEATS, START/STOP).
  • Pattern::matches_single — Level 1 shortcut for a single top-level observation against one SCO.
  • Pattern::evaluate_observed_data — build ObservationContext from observed-data.object_refs and evaluate against a bundle.
  • ObservationContext / TimestampedObservation — evaluation context with optional bundle for _ref dereference.
  • Pattern::matches_single_with_bundle — Level 1 evaluation with optional bundle for _ref dereference.
  • CustomSco — vendor/custom SCO types deserialize and evaluate (e.g. x-usb-device paths).
  • TimestampedObservation::atOption<StixTimestamp>; temporal patterns return MissingTimestamp when any observation lacks a timestamp.
  • Object-path resolver — full §9.8 paths: extension sections[*].entropy, ref lists resolves_to_refs[*].value, body_multipart[*].body_raw_ref.name, dst_ref.type/value, binary payload_bin, EXISTS on registry values, nested custom properties.
  • pattern::security — regex compile size limit (1 MiB) enforced during MATCHES evaluation.
  • Observed-data — embedded SRO members in deprecated objects are skipped (not an error).
  • Tests — manifest-driven SCO field coverage (tests/pattern_eval_sco_fields.rs, 276 cases), per-operator eval (tests/pattern_eval_operators.rs), every PatternMatchError path (tests/pattern_eval_errors.rs), §9.8 spec eval (tests/pattern_spec_eval.rs); 447 tests pass with pattern,serde.

Canonical printer, IndicatorPattern::Stix { ast } serde wiring, and fuzz_stix_pattern remain in the next Pattern Engine slice.

v0.18.0...v0.19.0