feat: reverse converter (rule reverse --from <dialect>) with a Lucene frontend - #371
Merged
Conversation
Add emit_rule_yaml / emit_collection_yaml as the inverse of parse_sigma_yaml. The emitter is a deterministic canonical form (sorted named detections, logsource custom fields, and custom attributes), reconstructs field|modifier keys, escapes literal wildcards in values while leaving re/cidr/fieldref values raw, and reuses the ConditionExpr rendering for the condition string.
Add raise_rule as the inverse of lower_rule: reconstruct the field|modifier surface each IrMatcher variant implies, collapse homogeneous value lists, keep conditions selector-preserving, and reject numeric dynamic-source references. Move the canonical ir_pattern_to_sigma into rsigma-ir (re-exported from rsigma-convert) so convert and the raise path share one reconstruction. Round-trip tested: parse -> lower -> raise -> lower is HIR-stable, and the full parse -> lower -> raise -> emit -> parse -> lower pivot preserves the HIR.
Add rsigma-convert::reverse, the mirror of the Backend engine: a Frontend trait plus a QueryDialect table drive a shared tokenizer and precedence-climbing boolean parser, and assemble a boolean tree of leaves into named Sigma selections and a condition (AND-merged selections, same-field OR value lists, negated branches as filters). raise_rule and emit_rule_yaml turn the resulting IrRule into Sigma YAML; reverse_collection converts a batch, collecting per-query errors. The Lucene reference frontend parses the query_string subset detection authors use (field:value with wildcards, quoted phrases, /regex/, [a TO b] ranges, comparison shorthand, field:(a OR b) groups, _exists_, keywords, AND/OR/NOT with grouping) and rejects boosting, fuzzy/proximity, and non-numeric ranges with a structured ConvertError.
A rule with no logsource fields previously emitted a bare `logsource:` key, which parses as null and the parser rejects (logsource must be a mapping). Emit an explicit empty mapping instead.
Convert an Elastic Lucene query into a draft Sigma rule. Reads the query from an argument, --file, or stdin; takes title/id/level/status/logsource hints a query cannot carry; and prints Sigma YAML (or writes it with -o). The emitted rule is parsed back before printing, so a rule that would not round-trip never reaches the operator. Joins the rule draft / discover-schemas authoring family.
Expose reverse conversion over MCP: from_lucene turns an Elastic Lucene query into a draft Sigma rule (YAML), taking the metadata and logsource a query cannot carry as parameters and returning inexpressible constructs as an error envelope. Brings the tool count to 13.
Add a golden corpus (query -> expected Sigma YAML) covering equality, AND/NOT filters, same-field OR value lists, regex, ranges and comparisons, value groups, _exists_, and keywords, driven through reverse_collection like the CLI. Add the fuzz_lucene_frontend target exercising the full untrusted path (tokenize, parse, assemble, raise, emit). Also fix quoted-phrase unescaping so a Lucene \\ inside quotes becomes a single literal backslash.
CHANGELOG entry for the reverse-conversion framework and Lucene frontend; rsigma-parser (emit), rsigma-ir (raise), rsigma-convert (reverse module) and rsigma-mcp (from_lucene tool) README updates; and a new rule from-lucene CLI docs page wired into the site nav.
mostafa
marked this pull request as draft
July 21, 2026 16:38
…ecting entry point Replace the per-dialect `rule from-lucene` / `from_lucene` surface with one entry point that selects the dialect, mirroring the forward `backend convert --target`: the CLI is now `rsigma rule reverse --from <dialect>` (a --from value enum) and the MCP tool is `reverse_convert` with a `dialect` parameter. This keeps the rule noun from sprouting a subcommand per dialect and scales cleanly as SPL/KQL/others land. Lucene remains the only dialect.
Add the reverse_convert tool call to the mcp-smoke surface (13 tools) and update the count in the docstring. Verified end to end over both transports.
--file is now repeatable and accepts files or directories: each file is one query, a directory contributes every query file it holds (recursively, filtered to the dialect's extensions; an explicitly named file is read regardless). Batch runs convert one rule per query, titled from the file name, and emit a multi-document stream (or one <name>.yml per query when -o is a directory). Unconvertible queries are reported and fail the run without dropping the rules that did convert.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a pluggable reverse-conversion framework, the mirror of the forward
Backendengine: a SIEM query is parsed into the intermediate representation, raised to a Sigma rule, and emitted as YAML. Elastic Lucene ships as the reference frontend.Frontendis the inverse ofBackend,raise_rulethe inverse oflower_rule, andemit_rule_yamlthe inverse ofparse_sigma_yaml.What's included
rsigma-parser—emit_rule_yaml/emit_collection_yaml, a deterministic canonical Sigma YAML emitter (sorted named detections, logsource custom fields, and custom attributes; reconstructedfield|modifierkeys; wildcard-escaped values with rawre/cidr/fieldref; empty logsource emitted as{}).rsigma-ir—raise_rule(IrRuletoSigmaRule): reconstructs the modifiers eachIrMatcherimplies, collapses homogeneous value lists, keeps conditions selector-preserving, and rejects numeric dynamic-source references. The canonicalir_pattern_to_sigmamoved here (re-exported fromrsigma-convert).rsigma-convert— areversemodule: aFrontendtrait plus aQueryDialecttable drive a shared tokenizer and precedence-climbing boolean parser;assemble_rulebuilds named selections and a condition (AND-merged selections, same-field OR value lists, negated branches as filters);reverse_collectionconverts a batch.LuceneFrontendparses the Lucenequery_stringsubset (field:valuewith wildcards, quoted phrases,/regex/,[a TO b]/{a TO b}ranges, comparison shorthand,field:(a OR b)groups,_exists_, keywords, andAND/OR/NOTwith grouping) and rejects boosting, fuzzy/proximity, and non-numeric ranges with a structured error.rsigmaCLI —rsigma rule reverse --from <dialect>: a single entry point that selects the dialect (mirroringbackend convert --target), so therulenoun does not sprout a subcommand per dialect. Reads a query from an argument,--file, or stdin; takes--title/--id/--level/--statusand--logsource-*hints; prints Sigma YAML (or-o); and parses the result back before printing so a rule that would not round-trip never reaches the operator.rsigma-mcp— areverse_converttool with adialectparameter (13 tools total).Reverse conversion is best-effort by design: a query carries no rule metadata, so the output is a reviewable skeleton, and inexpressible constructs are rejected rather than emitted as silently-wrong Sigma. Adding a new target is a
QueryDialecttable plus aFrontend::parse_atom(and a--from/dialectvalue).Tests
parse -> lower -> raise -> emit -> parse -> lowerpreserves the HIR).crates/rsigma-convert/tests/golden/lucene/) driven throughreverse_collection.fuzz_lucene_frontendtarget exercising the full untrusted path.cargo fmt --check,cargo clippy --workspace --all-targets --all-features -D warnings,cargo doc(warnings denied),cargo test --workspace --all-features(3480 tests), anddocmd build/validateall pass.Closes #348.