diff --git a/scan.py b/scan.py index f6ead8d..5774e64 100755 --- a/scan.py +++ b/scan.py @@ -790,6 +790,8 @@ def notify(settings: Settings, title: str, body: str) -> bool: log("WARN", "No APPRISE_URLS or APPRISE_CONFIG configured - notification skipped") return True + if len(title) > 200: + title = title[:197] + "..." log("INFO", f"Sending Apprise notification: {title} (title {len(title)} chars, body {len(body)} chars)") command = ["apprise", "-t", title, "-b", body, "-vv"] if settings.apprise_config: @@ -854,7 +856,7 @@ def build_body( MAX_NOTIFICATION_CHARS = 2500 -DISCORD_MAX_NOTIFICATION_CHARS = 1500 +DISCORD_MAX_NOTIFICATION_CHARS = 1200 def uses_discord(settings: Settings) -> bool: @@ -1006,11 +1008,11 @@ def build_other_containers_body( Each row is ``(image, container, total, fixable, unfixable)``. """ - lines = ["Other containers with findings:", ""] + lines = ["Other containers:"] for image, container, total, fixable, unfixable in rows: - label = f"{image} [{container}]" if container else image - lines.append(f"* {label}: {fixable}/{total} (fixable/total)") - lines += ["", f"Total: {sum(row[2] for row in rows)} CVE(s) in {len(rows)} container(s)"] + label = container or image + lines.append(f"- {label}: {fixable}/{total}") + lines.append(f"Total: {sum(row[2] for row in rows)} in {len(rows)}") return "\n".join(lines) @@ -1169,13 +1171,16 @@ def _split_body(body: str, max_chars: int) -> list[str]: current: list[str] = [] size = 0 for line in body.splitlines(): - while len(line) > max_chars: - if current: - parts.append("\n".join(current)) - current = [] - size = 0 - parts.append(line[:max_chars]) - line = line[max_chars:] + if len(line) > max_chars: + while len(line) > max_chars: + if current: + parts.append("\n".join(current)) + current = [] + size = 0 + chunk = line[:max_chars] + line = line[max_chars:] + parts.append(chunk) + continue line_size = len(line) + 1 if current and size + line_size > max_chars: parts.append("\n".join(current)) @@ -1216,7 +1221,8 @@ def send_notification_parts( original_title = title if len(title) > 200: original_title = title[:197] + "..." - parts = _split_body(body, notification_limit(settings)) + limit = min(notification_limit(settings), 1100) + parts = _split_body(body, limit) if len(parts) > 1: log("INFO", f"Sending {title} in {len(parts)} parts") ok = True diff --git a/tests/test_scan.py b/tests/test_scan.py index 2b39c53..05ca8cb 100644 --- a/tests/test_scan.py +++ b/tests/test_scan.py @@ -502,9 +502,9 @@ class ReportTests(unittest.TestCase): ("redis:7", "cache", 4, 0, 4), ] ) - self.assertIn("* nginx:1.27 [web-worker]: 2/3 (fixable/total)", body) - self.assertIn("* redis:7 [cache]: 0/4 (fixable/total)", body) - self.assertIn("Total: 7 CVE(s) in 2 container(s)", body) + self.assertIn("- web-worker: 2/3", body) + self.assertIn("- cache: 0/4", body) + self.assertIn("Total: 7 in 2", body) def test_build_missing_body_separates_missing_and_unscanned(self) -> None: body = scan.build_missing_body(["gone"], ["web"]) @@ -668,7 +668,7 @@ printf '%s\\n' "$*" >> "$APPRISE_LOG" self.assertIn("Container: web", notification) self.assertIn("CVE-2026-0001", notification) self.assertIn("Vulnerability scan: other containers", notification) - self.assertIn("redis:7 [cache]", notification) + self.assertIn("cache: 1/1", notification) self.assertIn("1/1 (fixable/total)", notification) # The important container must not also appear in the summary. self.assertNotIn("testphp-php [web]", notification)