From 02d44c4ed3696995815b3bd248817a78e2765697 Mon Sep 17 00:00:00 2001 From: elhoim Date: Thu, 24 Sep 2026 11:19:11 +0000 Subject: [PATCH] Report YAML syntax errors instead of crashing check/convert A YAML syntax error in a rule file raised yaml.YAMLError out of load_rules(). check and convert only handle SigmaError, so the user got a Python traceback, and 'sigma check --junitxml' did not write its report. load_rules() now turns the YAML error into a SigmaCollectionError, so the existing handlers print 'Check error: ...' / 'Error while converting: ...' (with file, line and column) and exit 1, and check writes the JUnit report. Co-Authored-By: Claude Opus 5.5 (1M context) --- sigma/cli/rules.py | 42 ++++++++++++++++++++++++------------------ tests/test_check.py | 16 ++++++++++++++++ tests/test_convert.py | 9 +++++++++ 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/sigma/cli/rules.py b/sigma/cli/rules.py index e4c0cd8..1a8879c 100644 --- a/sigma/cli/rules.py +++ b/sigma/cli/rules.py @@ -1,7 +1,9 @@ from pathlib import Path from sys import stderr import click +import yaml from sigma.collection import SigmaCollection +from sigma.exceptions import SigmaCollectionError def load_rules(input, file_pattern): @@ -10,27 +12,31 @@ def load_rules(input, file_pattern): """ rule_collection = SigmaCollection([], []) - for path in list(input): - if path == Path("-"): - rule_collection = SigmaCollection.merge([ - rule_collection, - SigmaCollection.from_yaml(click.get_text_stream("stdin")) - ]) - else: - rule_paths = SigmaCollection.resolve_paths( - [path], - recursion_pattern="**/" + file_pattern, - ) - with click.progressbar( - list(rule_paths), label="Parsing Sigma rules", file=stderr - ) as progress_rule_paths: + try: + for path in list(input): + if path == Path("-"): rule_collection = SigmaCollection.merge([ rule_collection, - SigmaCollection.load_ruleset( - progress_rule_paths, - collect_errors=True, - ) + SigmaCollection.from_yaml(click.get_text_stream("stdin")) ]) + else: + rule_paths = SigmaCollection.resolve_paths( + [path], + recursion_pattern="**/" + file_pattern, + ) + with click.progressbar( + list(rule_paths), label="Parsing Sigma rules", file=stderr + ) as progress_rule_paths: + rule_collection = SigmaCollection.merge([ + rule_collection, + SigmaCollection.load_ruleset( + progress_rule_paths, + collect_errors=True, + ) + ]) + except yaml.YAMLError as e: + # Report YAML syntax errors like other Sigma errors instead of crashing with a traceback. + raise SigmaCollectionError(f"YAML syntax error: {e}") from e rule_collection.resolve_rule_references() diff --git a/tests/test_check.py b/tests/test_check.py index 006da08..fb5534f 100644 --- a/tests/test_check.py +++ b/tests/test_check.py @@ -149,3 +149,19 @@ def test_check_cli_generates_junitxml(tmp_path): assert out.exists() tree = ET.parse(str(out)) assert tree.getroot().tag == "testsuites" + + +def test_check_yaml_syntax_error_junitxml(tmp_path): + """A YAML syntax error is reported as check error and still produces the JUnit report.""" + rules = tmp_path / "rules" + rules.mkdir() + (rules / "broken.yml").write_text("title: [unclosed\n") + out = tmp_path / "report.xml" + cli = CliRunner() + result = cli.invoke(check, ["--junitxml", str(out), str(rules)]) + assert result.exit_code == 1 + assert "Check error: YAML syntax error" in result.output + assert "broken.yml" in result.output + tree = ET.parse(str(out)) + failures = tree.getroot().findall(".//failure") + assert any("YAML syntax error" in (f.text or "") for f in failures) diff --git a/tests/test_convert.py b/tests/test_convert.py index a39794e..92a98de 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -46,6 +46,15 @@ def test_convert_invalid_rule(): assert "at least one condition" in result.stderr +def test_convert_yaml_syntax_error(tmp_path): + (tmp_path / "broken.yml").write_text("title: [unclosed\n") + cli = CliRunner() + result = cli.invoke(convert, ["-t", "text_query_test", str(tmp_path)]) + assert result.exit_code == 1 + assert "Error while converting: YAML syntax error" in result.stderr + assert "broken.yml" in result.stderr + + def test_convert_stdin(): cli = CliRunner() with open("tests/files/valid/sigma_rule.yml", "rt") as yml_file: