fix(overviews): exclude muted findings from severity overview (#9283)

This commit is contained in:
Víctor Fernández Poyatos
2025-11-20 16:29:20 +01:00
committed by GitHub
parent 6426558b18
commit 789fc84e31
5 changed files with 36 additions and 31 deletions

View File

@@ -28,6 +28,7 @@ All notable changes to the **Prowler API** are documented in this file.
### Fixed
- Scans no longer fail when findings have UIDs exceeding 300 characters; such findings are now skipped with detailed logging [(#9246)](https://github.com/prowler-cloud/prowler/pull/9246)
- Refresh output report timestamps for each scan [(#9272)](https://github.com/prowler-cloud/prowler/pull/9272)
- Severity overview endpoint now ignores muted findings as expected [(#9283)](https://github.com/prowler-cloud/prowler/pull/9283)
### Security
- Django updated to the latest 5.1 security release, 5.1.14, due to problems with potential [SQL injection](https://github.com/prowler-cloud/prowler/security/dependabot/113) and [denial-of-service vulnerability](https://github.com/prowler-cloud/prowler/security/dependabot/114) [(#9176)](https://github.com/prowler-cloud/prowler/pull/9176)

View File

@@ -820,7 +820,8 @@ class ScanSummarySeverityFilter(ScanSummaryFilter):
elif value == OverviewStatusChoices.PASS:
return queryset.annotate(status_count=F("_pass"))
else:
return queryset.annotate(status_count=F("total"))
# Exclude muted findings by default
return queryset.annotate(status_count=F("_pass") + F("fail"))
def filter_status_in(self, queryset, name, value):
# Validate the status values
@@ -829,7 +830,7 @@ class ScanSummarySeverityFilter(ScanSummaryFilter):
if status_val not in valid_statuses:
raise ValidationError(f"Invalid status value: {status_val}")
# If all statuses or no valid statuses, use total
# If all statuses or no valid statuses, exclude muted findings (pass + fail)
if (
set(value)
>= {
@@ -838,7 +839,7 @@ class ScanSummarySeverityFilter(ScanSummaryFilter):
}
or not value
):
return queryset.annotate(status_count=F("total"))
return queryset.annotate(status_count=F("_pass") + F("fail"))
# Build the sum expression based on status values
sum_expression = None
@@ -856,7 +857,7 @@ class ScanSummarySeverityFilter(ScanSummaryFilter):
sum_expression = sum_expression + field_expr
if sum_expression is None:
return queryset.annotate(status_count=F("total"))
return queryset.annotate(status_count=F("_pass") + F("fail"))
return queryset.annotate(status_count=sum_expression)

View File

@@ -6280,10 +6280,10 @@ class TestOverviewViewSet:
response = authenticated_client.get(reverse("overview-providers"))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()["data"]) == 1
assert response.json()["data"][0]["attributes"]["findings"]["total"] == 4
assert response.json()["data"][0]["attributes"]["findings"]["total"] == 9
assert response.json()["data"][0]["attributes"]["findings"]["pass"] == 2
assert response.json()["data"][0]["attributes"]["findings"]["fail"] == 1
assert response.json()["data"][0]["attributes"]["findings"]["muted"] == 1
assert response.json()["data"][0]["attributes"]["findings"]["muted"] == 6
# Aggregated resources include all AWS providers present in the tenant
assert response.json()["data"][0]["attributes"]["resources"]["total"] == 3
@@ -6335,10 +6335,10 @@ class TestOverviewViewSet:
assert len(data) == 1
attributes = data[0]["attributes"]
assert attributes["findings"]["total"] == 10
assert attributes["findings"]["total"] == 15
assert attributes["findings"]["pass"] == 5
assert attributes["findings"]["fail"] == 3
assert attributes["findings"]["muted"] == 2
assert attributes["findings"]["muted"] == 7
assert attributes["resources"]["total"] == 4
def test_overview_providers_count(
@@ -6791,15 +6791,14 @@ class TestOverviewViewSet:
assert response.status_code == status.HTTP_200_OK
# Only two different services
assert len(response.json()["data"]) == 2
# Fixed data from the fixture, TODO improve this at some point with something more dynamic
# Fixed data from the fixture
service1_data = response.json()["data"][0]
service2_data = response.json()["data"][1]
assert service1_data["id"] == "service1"
assert service2_data["id"] == "service2"
# TODO fix numbers when muted_findings filter is fixed
assert service1_data["attributes"]["total"] == 3
assert service2_data["attributes"]["total"] == 1
assert service1_data["attributes"]["total"] == 7
assert service2_data["attributes"]["total"] == 2
assert service1_data["attributes"]["pass"] == 1
assert service2_data["attributes"]["pass"] == 1
@@ -6807,8 +6806,8 @@ class TestOverviewViewSet:
assert service1_data["attributes"]["fail"] == 1
assert service2_data["attributes"]["fail"] == 0
assert service1_data["attributes"]["muted"] == 1
assert service2_data["attributes"]["muted"] == 0
assert service1_data["attributes"]["muted"] == 5
assert service2_data["attributes"]["muted"] == 1
def test_overview_findings_provider_id_in_filter(
self, authenticated_client, tenants_fixture, providers_fixture
@@ -6918,6 +6917,7 @@ class TestOverviewViewSet:
tenant=tenant,
)
# Muted findings should be excluded from severity counts
ScanSummary.objects.create(
tenant=tenant,
scan=scan1,
@@ -6927,8 +6927,8 @@ class TestOverviewViewSet:
region="region-a",
_pass=4,
fail=4,
muted=0,
total=8,
muted=3,
total=11,
)
ScanSummary.objects.create(
tenant=tenant,
@@ -6939,8 +6939,8 @@ class TestOverviewViewSet:
region="region-b",
_pass=2,
fail=2,
muted=0,
total=4,
muted=2,
total=6,
)
ScanSummary.objects.create(
tenant=tenant,
@@ -6951,8 +6951,8 @@ class TestOverviewViewSet:
region="region-c",
_pass=1,
fail=2,
muted=0,
total=3,
muted=5,
total=8,
)
single_response = authenticated_client.get(
@@ -6961,6 +6961,7 @@ class TestOverviewViewSet:
)
assert single_response.status_code == status.HTTP_200_OK
single_attributes = single_response.json()["data"]["attributes"]
# Should only count pass + fail, excluding muted (3 muted in high, 2 in medium)
assert single_attributes["high"] == 8
assert single_attributes["medium"] == 4
assert single_attributes["critical"] == 0
@@ -6971,6 +6972,7 @@ class TestOverviewViewSet:
)
assert combined_response.status_code == status.HTTP_200_OK
combined_attributes = combined_response.json()["data"]["attributes"]
# Should only count pass + fail, excluding muted (5 muted in critical)
assert combined_attributes["high"] == 8
assert combined_attributes["medium"] == 4
assert combined_attributes["critical"] == 3

View File

@@ -4198,7 +4198,7 @@ class OverviewViewSet(BaseRLSViewSet):
# Load only required fields
queryset = self.get_queryset().only(
"tenant_id", "scan_id", "severity", "fail", "_pass", "total"
"tenant_id", "scan_id", "severity", "fail", "_pass", "muted"
)
filtered_queryset = self.filter_queryset(queryset)
@@ -4224,7 +4224,8 @@ class OverviewViewSet(BaseRLSViewSet):
if "status_count" in filtered_queryset.query.annotations:
sum_expression = Sum("status_count")
else:
sum_expression = Sum("total")
# Exclude muted findings by default
sum_expression = Sum(F("_pass") + F("fail"))
severity_counts = (
filtered_queryset.values("severity")

View File

@@ -1108,8 +1108,8 @@ def scan_summaries_fixture(tenants_fixture, providers_fixture):
region="region1",
_pass=1,
fail=0,
muted=0,
total=1,
muted=2,
total=3,
new=1,
changed=0,
unchanged=0,
@@ -1117,7 +1117,7 @@ def scan_summaries_fixture(tenants_fixture, providers_fixture):
fail_changed=0,
pass_new=1,
pass_changed=0,
muted_new=0,
muted_new=2,
muted_changed=0,
scan=scan,
)
@@ -1130,8 +1130,8 @@ def scan_summaries_fixture(tenants_fixture, providers_fixture):
region="region2",
_pass=0,
fail=1,
muted=1,
total=2,
muted=3,
total=4,
new=2,
changed=0,
unchanged=0,
@@ -1139,7 +1139,7 @@ def scan_summaries_fixture(tenants_fixture, providers_fixture):
fail_changed=0,
pass_new=0,
pass_changed=0,
muted_new=1,
muted_new=3,
muted_changed=0,
scan=scan,
)
@@ -1152,8 +1152,8 @@ def scan_summaries_fixture(tenants_fixture, providers_fixture):
region="region1",
_pass=1,
fail=0,
muted=0,
total=1,
muted=1,
total=2,
new=1,
changed=0,
unchanged=0,
@@ -1161,7 +1161,7 @@ def scan_summaries_fixture(tenants_fixture, providers_fixture):
fail_changed=0,
pass_new=1,
pass_changed=0,
muted_new=0,
muted_new=1,
muted_changed=0,
scan=scan,
)