fix: Banner checks no longer default to PASS when untested

20 checks were defaulting to PASS when no violation was found,
even if the scanner couldn't actually test them. Now:
- Phase-based checks (tracking/cookies): absence = PASS (correct)
- UI checks: only PASS if banner_checks actually ran
- If banner not detected: everything except banner_detected = FAIL

This prevents false 100% scores when violations exist but the
text→code mapping doesn't cover them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-10 08:32:05 +02:00
parent 561150b5a8
commit 80ae196853
+28 -6
View File
@@ -38,7 +38,10 @@ def map_scan_to_checks(scan_result: dict) -> dict:
# For checks whose check_key appears in violations → failed # For checks whose check_key appears in violations → failed
# For checks whose check_key appears only in passes → passed # For checks whose check_key appears only in passes → passed
# For checks where neither → assume passed (not tested = no finding) # For checks where neither:
# - Phase-based checks (tracking/cookies) → PASS (absence = good)
# - Banner UI checks → PASS only if banner was detected and
# the scanner actually ran the relevant check
if is_violation_key: if is_violation_key:
passed = False passed = False
matched_text = violation_codes[key] matched_text = violation_codes[key]
@@ -46,12 +49,20 @@ def map_scan_to_checks(scan_result: dict) -> dict:
passed = True passed = True
matched_text = pass_codes.get(key, "") matched_text = pass_codes.get(key, "")
else: else:
# Key not found in violations or explicit passes. banner_detected = scan_result.get("banner_detected", False)
# If the scan ran (banner detected) → assume passed.
# If banner not detected → only banner_detected fails.
passed = scan_result.get("banner_detected", False) or key == "banner_detected"
if key == "banner_detected": if key == "banner_detected":
passed = scan_result.get("banner_detected", False) passed = banner_detected
elif key in _ABSENCE_IS_PASS:
# For these checks, no violation = passed (e.g. no tracking cookies)
passed = True
elif banner_detected:
# Banner was detected but this specific check produced no result.
# If the scanner ran banner_checks → assume checked and passed.
# If banner_checks is empty → scanner couldn't test → not passed.
has_banner_results = bool(scan_result.get("banner_checks", {}).get("violations") is not None)
passed = has_banner_results
else:
passed = False
matched_text = "" matched_text = ""
# L2 checks are skipped if their parent L1 failed # L2 checks are skipped if their parent L1 failed
@@ -213,6 +224,17 @@ def _collect_pass_codes(scan: dict) -> dict[str, str]:
return passes return passes
# Checks where absence of a violation means PASS (not "untested")
# These are phase-based checks: if no tracking was detected, that's good.
_ABSENCE_IS_PASS = {
"tracking_before_consent",
"cookies_before_consent",
"tracking_after_reject",
"google_consent_mode_defaults",
"banner_language_mismatch",
"cookie_wall",
}
_TRACKING_COOKIE_PREFIXES = ( _TRACKING_COOKIE_PREFIXES = (
"_ga", "_gid", "_fbp", "_fbc", "IDE", "_gcl", "fr", "_pin", "_ga", "_gid", "_fbp", "_fbc", "IDE", "_gcl", "fr", "_pin",
"_tt_", "li_sugr", "_hj", "mp_", "ajs_", "_clck", "_clsk", "_tt_", "li_sugr", "_hj", "mp_", "ajs_", "_clck", "_clsk",