From 88cec9566902f7ab905d68af3f791545ac7dc637 Mon Sep 17 00:00:00 2001 From: SkxOverKill Date: Tue, 11 Aug 2026 18:00:22 +0530 Subject: [PATCH] Fix crash of 'analyze attack' with rulesets without ATT&CK tags If no rule carries an attack tag, calculate_attack_scores() returns an empty dict. The ATT&CK Navigator layer generation then evaluated max(scores.values()) on the empty dict, which raised 'ValueError: max() iterable argument is empty' and aborted the command with a raw traceback and exit code 1, without writing the output file. Additionally, an explicitly set --max-score 0 was ignored because the previous expression checked truthiness ('max_score or ...') instead of None. Resolve the maximum to the explicit --max-score if given, otherwise to the highest score present, falling back to 0 for empty rulesets. --- sigma/cli/analyze.py | 2 +- tests/files/noattack/plain_rule.yml | 9 +++++++++ tests/test_analyze.py | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/files/noattack/plain_rule.yml diff --git a/sigma/cli/analyze.py b/sigma/cli/analyze.py index cb3f423..adbe724 100644 --- a/sigma/cli/analyze.py +++ b/sigma/cli/analyze.py @@ -153,7 +153,7 @@ def analyze_attack( max_color, ], "minValue": min_score, - "maxValue": max_score or max(scores.values()), + "maxValue": max_score if max_score is not None else (max(scores.values()) if scores else 0), }, "techniques": layer_techniques, } diff --git a/tests/files/noattack/plain_rule.yml b/tests/files/noattack/plain_rule.yml new file mode 100644 index 0000000..3b4f099 --- /dev/null +++ b/tests/files/noattack/plain_rule.yml @@ -0,0 +1,9 @@ +title: Plain rule without ATT&CK tags +id: 11111111-2222-3333-4444-555555555555 +status: test +logsource: + category: process_creation +detection: + selection: + Image|endswith: '\evil.exe' + condition: selection diff --git a/tests/test_analyze.py b/tests/test_analyze.py index 65106db..750df82 100644 --- a/tests/test_analyze.py +++ b/tests/test_analyze.py @@ -87,6 +87,25 @@ def test_attack_generate_min_color(): assert "#123456" in result.stdout +def test_attack_generate_no_attack_tags(): + cli = CliRunner() + result = cli.invoke( + analyze_attack, ["max", "-", "tests/files/noattack"] + ) + assert result.exit_code == 0 + assert '"maxValue": 0' in result.stdout + assert '"techniques": []' in result.stdout + + +def test_attack_generate_max_score_zero(): + cli = CliRunner() + result = cli.invoke( + analyze_attack, ["--max-score", "0", "max", "-", "tests/files/valid"] + ) + assert result.exit_code == 0 + assert '"maxValue": 0' in result.stdout + + def test_attack_generate_no_subtechniques(): cli = CliRunner() result = cli.invoke(