fix(compliance): skip MANUAL findings in section tally to avoid KeyError (#11823)

Co-authored-by: pedrooot <pedromarting3@gmail.com>
This commit is contained in:
Sanjay Santhanam
2026-07-03 03:19:32 -07:00
committed by GitHub
parent 0cf6f2f83e
commit 55924d8150
3 changed files with 45 additions and 0 deletions
+8
View File
@@ -2,6 +2,14 @@
All notable changes to the **Prowler SDK** are documented in this file. All notable changes to the **Prowler SDK** are documented in this file.
## [5.32.1] (Prowler UNRELEASED)
### 🐞 Fixed
- `KeyError: 'MANUAL'` crash while rendering the compliance summary table (e.g. CIS Microsoft 365) when a framework has manual, checks-less requirements with a Level 1/Level 2 profile; `MANUAL` findings are now skipped in the PASS/FAIL section tally instead of raising [(#11822)](https://github.com/prowler-cloud/prowler/issues/11822)
---
## [5.32.0] (Prowler v5.32.0) ## [5.32.0] (Prowler v5.32.0)
### 🚀 Added ### 🚀 Added
@@ -395,6 +395,12 @@ def accumulate_group_status(
) -> None: ) -> None:
"""Count a finding once per group, upgrading a counted PASS to FAIL on conflict (mutates ``counts``/``seen``).""" """Count a finding once per group, upgrading a counted PASS to FAIL on conflict (mutates ``counts``/``seen``)."""
previous = seen.get(index) previous = seen.get(index)
if status == "MANUAL":
# MANUAL findings come from manual, checks-less requirements and are
# informational only: they have no PASS/FAIL/Muted column in the section
# tally, so counting them would raise KeyError on counts[status] += 1.
# Skip them (an unexpected status still raises loudly below).
return
if previous is None: if previous is None:
seen[index] = status seen[index] = status
counts[status] += 1 counts[status] += 1
@@ -351,6 +351,37 @@ class Test_accumulate_group_status:
with pytest.raises(KeyError): with pytest.raises(KeyError):
accumulate_group_status(0, "Muted", counts, {}) accumulate_group_status(0, "Muted", counts, {})
def test_manual_status_is_ignored_not_counted(self):
# A MANUAL finding (from a manual, checks-less requirement) has no
# PASS/FAIL/Muted column: it must be skipped, not raise KeyError, and
# not appear in the tally. Regression test for the M365 CIS compliance
# crash "KeyError: 'MANUAL'" (issue #11822).
counts = {"FAIL": 0, "PASS": 0}
seen = {}
accumulate_group_status(0, "MANUAL", counts, seen)
assert counts == {"FAIL": 0, "PASS": 0}
assert seen == {}
def test_manual_mixed_with_pass_and_fail(self):
# MANUAL findings interleaved with real PASS/FAIL ones only skip
# themselves; the PASS/FAIL tally is unaffected.
counts = {"FAIL": 0, "PASS": 0}
seen = {}
accumulate_group_status(0, "MANUAL", counts, seen)
accumulate_group_status(1, "PASS", counts, seen)
accumulate_group_status(2, "FAIL", counts, seen)
accumulate_group_status(3, "MANUAL", counts, seen)
assert counts == {"FAIL": 1, "PASS": 1}
def test_manual_ignored_on_counts_with_muted_key(self):
# MANUAL is skipped regardless of the counts shape (e.g. the universal
# table's PASS/FAIL/Muted buckets), never creating a "MANUAL" key.
counts = {"FAIL": 0, "PASS": 0, "Muted": 0}
seen = {}
accumulate_group_status(0, "MANUAL", counts, seen)
assert counts == {"FAIL": 0, "PASS": 0, "Muted": 0}
assert "MANUAL" not in counts
class Test_apply_config_status: class Test_apply_config_status:
def test_none_config_status_keeps_finding(self): def test_none_config_status_keeps_finding(self):