more changes

This commit is contained in:
David Duran Rosich committed 2026-10-24 23:55:11 +02:00
1 parent a8241fa648
commit 8a5c712ef0
3 files changed
+26 -21

No files matched your search

+7 -6
View File
@@ -25,12 +25,13 @@ host only needs Docker. JSON reports are kept on disk for auditing.
alert. alert.
- `FAIL_ON` (default `HIGH`) – when the script should exit with code `1` - `FAIL_ON` (default `HIGH`) – when the script should exit with code `1`
(useful for CI). (useful for CI).
3. If any CVE reaches `NOTIFY_MIN_SEVERITY`, notifications are sent. When 3. Notifications are sent. When `IMPORTANT_CONTAINERS` is set, each named
`IMPORTANT_CONTAINERS` is set, each named container gets its own complete container gets its own complete CVE list (showing `C X/Y ...` where X is
CVE list (split into fixable and unfixable), missing important containers fixable and Y is total per severity, plus `Total: fixable/total`), missing
are reported, and every other container is summarised in one notification. important containers are reported, and every other container is summarised as
Without `IMPORTANT_CONTAINERS`, a single aggregated notification lists each `fixable/total`. Without `IMPORTANT_CONTAINERS`, a single aggregated
CVE with the affected packages, containers, and images. notification lists each CVE with the affected packages, containers, and
images.
## Quick start ## Quick start
+12 -10
View File
@@ -854,7 +854,7 @@ def build_body(
MAX_NOTIFICATION_CHARS = 2500 MAX_NOTIFICATION_CHARS = 2500
DISCORD_MAX_NOTIFICATION_CHARS = 1800 DISCORD_MAX_NOTIFICATION_CHARS = 1500
def uses_discord(settings: Settings) -> bool: def uses_discord(settings: Settings) -> bool:
@@ -973,12 +973,19 @@ def build_important_body(
entries = cve_entries(result, severity_rank(min_severity)) entries = cve_entries(result, severity_rank(min_severity))
total, fixable, unfixable = split_counts(entries) total, fixable, unfixable = split_counts(entries)
counts = severity_counts(entries) counts = severity_counts(entries)
fixable_counts = {severity: 0 for severity in REPORTED_SEVERITIES}
for entry in entries:
if entry.fixable:
fixable_counts[entry.severity] += 1
lines = [ lines = [
f"Image: {target.image}", f"Image: {target.image}",
f"Container: {container}", f"Container: {container}",
" | ".join(f"{name.title()}: {counts[name]}" for name in REPORTED_SEVERITIES), " | ".join(
f"Total: {total} (fixable: {fixable}, unfixable: {unfixable})", f"{name[0]} {fixable_counts[name]}/{counts[name]}"
for name in REPORTED_SEVERITIES
),
f"Total: {fixable}/{total} (fixable/total)",
"", "",
"CVEs:", "CVEs:",
] ]
@@ -1000,15 +1007,10 @@ def build_other_containers_body(
Each row is ``(image, container, total, fixable, unfixable)``. Each row is ``(image, container, total, fixable, unfixable)``.
""" """
lines = ["Other containers with findings:", ""] lines = ["Other containers with findings:", ""]
total_all = 0
for image, container, total, fixable, unfixable in rows: for image, container, total, fixable, unfixable in rows:
total_all += total
label = f"{image} [{container}]" if container else image label = f"{image} [{container}]" if container else image
lines.append( lines.append(f"* {label}: {fixable}/{total} (fixable/total)")
f"* {label}: {total} total " lines += ["", f"Total: {sum(row[2] for row in rows)} CVE(s) in {len(rows)} container(s)"]
f"({fixable} fixable, {unfixable} unfixable)"
)
lines += ["", f"Total: {total_all} CVE(s) in {len(rows)} container(s)"]
return "\n".join(lines) return "\n".join(lines)
+7 -5
View File
@@ -475,7 +475,8 @@ class ReportTests(unittest.TestCase):
) )
self.assertIn("Image: nginx:1.27", body) self.assertIn("Image: nginx:1.27", body)
self.assertIn("Container: web", body) self.assertIn("Container: web", body)
self.assertIn("Total: 3 (fixable: 2, unfixable: 1)", body) self.assertIn("C 1/1 | H 1/2 | M 0/0 | L 0/0", body)
self.assertIn("Total: 2/3 (fixable/total)", body)
self.assertIn("CVE-1", body) self.assertIn("CVE-1", body)
self.assertIn("CVE-2", body) self.assertIn("CVE-2", body)
self.assertIn("CVE-5", body) self.assertIn("CVE-5", body)
@@ -491,7 +492,8 @@ class ReportTests(unittest.TestCase):
self.assertIn("CVE-1", body) self.assertIn("CVE-1", body)
self.assertNotIn("CVE-2", body) self.assertNotIn("CVE-2", body)
self.assertNotIn("CVE-5", body) self.assertNotIn("CVE-5", body)
self.assertIn("Total: 1 (fixable: 1, unfixable: 0)", body) self.assertIn("C 1/1 | H 0/0 | M 0/0 | L 0/0", body)
self.assertIn("Total: 1/1 (fixable/total)", body)
def test_build_other_containers_body_has_counts_per_container(self) -> None: def test_build_other_containers_body_has_counts_per_container(self) -> None:
body = scan.build_other_containers_body( body = scan.build_other_containers_body(
@@ -500,8 +502,8 @@ class ReportTests(unittest.TestCase):
("redis:7", "cache", 4, 0, 4), ("redis:7", "cache", 4, 0, 4),
] ]
) )
self.assertIn("* nginx:1.27 [web-worker]: 3 total (2 fixable, 1 unfixable)", body) self.assertIn("* nginx:1.27 [web-worker]: 2/3 (fixable/total)", body)
self.assertIn("* redis:7 [cache]: 4 total (0 fixable, 4 unfixable)", body) self.assertIn("* redis:7 [cache]: 0/4 (fixable/total)", body)
self.assertIn("Total: 7 CVE(s) in 2 container(s)", body) self.assertIn("Total: 7 CVE(s) in 2 container(s)", body)
def test_build_missing_body_separates_missing_and_unscanned(self) -> None: def test_build_missing_body_separates_missing_and_unscanned(self) -> None:
@@ -667,7 +669,7 @@ printf '%s\\n' "$*" >> "$APPRISE_LOG"
self.assertIn("CVE-2026-0001", notification) self.assertIn("CVE-2026-0001", notification)
self.assertIn("Vulnerability scan: other containers", notification) self.assertIn("Vulnerability scan: other containers", notification)
self.assertIn("redis:7 [cache]", notification) self.assertIn("redis:7 [cache]", notification)
self.assertIn("(1 fixable, 0 unfixable)", notification) self.assertIn("1/1 (fixable/total)", notification)
# The important container must not also appear in the summary. # The important container must not also appear in the summary.
self.assertNotIn("testphp-php [web]", notification) self.assertNotIn("testphp-php [web]", notification)