"""Cross-Browser-Befunde — deterministische Sicht über die Browser-Matrix. KEINE zweite Engine, kein LLM: rein eine Auswertung der bereits vom consent-tester gemessenen Per-Engine-Ergebnisse (siehe [[feedback_agents_delegate_not_reimplement]]). Findet Inkonsistenzen ZWISCHEN den Browsern und ordnet sie ein — der zentrale Mehrwert gegenüber einem Einzel-Browser-Scan: * Cookies vor Consent / „Ablehnen“ nicht universell respektiert? * Browser-Tracking-Schutz (Safari/ITP, Brave/Shields, Firefox/ETP) maskiert Verstöße clientseitig → ein „sauberes" Ergebnis in einer strengen Engine ist KEIN Compliance-Beleg; maßgeblich sind die nachgiebigen Engines (Chrome/Edge/Chromium), in denen tatsächlich gesetzt wird. """ from __future__ import annotations from typing import Any # Welcher Browser-Schutzmechanismus greift je Profil — für die Einordnung, # warum sich Engines unterscheiden (nicht zum Bewerten der Compliance). _PROTECTION = [ ("brave", "Brave-Shields"), ("webkit", "Safari/ITP"), ("iphone", "Safari/ITP"), ("safari", "Safari/ITP"), ("firefox", "Firefox/ETP"), ("gecko", "Firefox/ETP"), ] def _protection_label(row: dict) -> str: key = f"{row.get('profile_id', '')} {row.get('engine', '')}".lower() for needle, label in _PROTECTION: if needle in key: return label return "" # blink/chrome/edge = nachgiebig (kein client-seitiger Schutz) def _labels(rows: list[dict]) -> list[str]: return [r.get("label") or r.get("profile_id") or "?" for r in rows] def build_cross_findings(matrix: dict | None) -> list[dict]: """Liefert eine priorisierte Liste von Cross-Browser-Befunden. Befund-Form: {title, detail, severity, affected: [labels], measure}. Nur Engines MIT Messdaten werden verglichen (Fehler-/„nicht verfügbar"- Zeilen fließen nur als Coverage-Hinweis ein).""" rows_all = (matrix or {}).get("browser_matrix") or [] data = [r for r in rows_all if r.get("summary")] missing = [r for r in rows_all if not r.get("summary")] out: list[dict] = [] if not data: return out def _s(r: dict) -> dict: return r.get("summary") or {} pre_yes = [r for r in data if (_s(r).get("cookies_before_consent") or 0) > 0] pre_no = [r for r in data if (_s(r).get("cookies_before_consent") or 0) == 0] rej_bad = [r for r in data if _s(r).get("reject_respected") is False] rej_ok = [r for r in data if _s(r).get("reject_respected") is True] # ── Cookies vor der Einwilligung ───────────────────────────────────── if pre_yes and not pre_no: out.append({ "title": "Cookies vor der Einwilligung — in allen Browsern", "detail": "In jeder getesteten Engine werden vor einer aktiven " "Einwilligung Cookies gesetzt.", "severity": "HIGH", "affected": _labels(pre_yes), "measure": "Tracking-/Marketing-Cookies erst nach aktiver " "Einwilligung setzen (§ 25 Abs. 1 TDDDG).", }) elif pre_yes and pre_no: masked = sorted({_protection_label(r) for r in pre_no if _protection_label(r)}) hint = (f" Die unauffälligen Engines ({', '.join(_labels(pre_no))}) " f"unterdrücken die Cookies vermutlich clientseitig " f"({', '.join(masked)}) — das ist KEIN Compliance-Beleg." if masked else "") out.append({ "title": "Cookies vor Einwilligung — nur in manchen Browsern", "detail": f"Cookies vor Consent in {', '.join(_labels(pre_yes))}, " f"nicht in {', '.join(_labels(pre_no))}.{hint}", "severity": "HIGH", "affected": _labels(pre_yes), "measure": "Server-/skriptseitig auf Consent gaten statt auf den " "Tracking-Schutz einzelner Browser zu vertrauen.", }) # ── „Ablehnen“ respektiert? ────────────────────────────────────────── if rej_bad and not rej_ok: out.append({ "title": "„Ablehnen“ wird in keinem Browser respektiert", "detail": "Nach Klick auf „Ablehnen“ verbleiben in jeder Engine " "Verstöße oder neue Tracker.", "severity": "HIGH", "affected": _labels(rej_bad), "measure": "Reject-Handler muss alle nicht-essentiellen Skripte/" "Cookies tatsächlich stoppen (Art. 7 Abs. 3 DSGVO).", }) elif rej_bad and rej_ok: masked = sorted({_protection_label(r) for r in rej_ok if _protection_label(r)}) hint = (f" Dass {', '.join(_labels(rej_ok))} sauber wirken, liegt " f"vermutlich am Browser-Schutz ({', '.join(masked)}), nicht am " f"Banner." if masked else "") out.append({ "title": "„Ablehnen“ wird nicht in allen Browsern respektiert", "detail": f"Verstoß nach Ablehnen in {', '.join(_labels(rej_bad))}; " f"sauber in {', '.join(_labels(rej_ok))}.{hint}", "severity": "HIGH", "affected": _labels(rej_bad), "measure": "Reject-Handler universell wirksam machen — unabhängig " "vom Tracking-Schutz des Browsers.", }) # ── Oberfläche (Banner-Links) durchgängig fehlend ──────────────────── if all(not _s(r).get("surface", {}).get("has_impressum_link") for r in data): out.append({ "title": "Impressum-Link im Banner fehlt (alle Browser)", "detail": "In keiner Engine ist aus dem Banner ein Impressum " "erreichbar.", "severity": "MEDIUM", "affected": _labels(data), "measure": "Impressum-Link im Banner ergänzen (§ 5 DDG).", }) if all(not _s(r).get("surface", {}).get("has_dse_link") for r in data): out.append({ "title": "Datenschutz-Link im Banner fehlt (alle Browser)", "detail": "In keiner Engine ist aus dem Banner die " "Datenschutzerklärung erreichbar.", "severity": "MEDIUM", "affected": _labels(data), "measure": "Link zur Datenschutzerklärung im Banner ergänzen " "(Art. 13 DSGVO).", }) # ── Coverage-Hinweis: nicht getestete Browser ──────────────────────── if missing: out.append({ "title": "Nicht alle Browser getestet", "detail": f"{len(missing)} Browser nicht verfügbar " f"({', '.join(_labels(missing))}). Echtes Brave/Chrome/" f"Edge laufen nur auf dem amd64-Server, nicht in der " f"arm64-Dev-Umgebung.", "severity": "LOW", "affected": _labels(missing), "measure": "Für vollständige Abdeckung auf dem Produktiv-Server " "(amd64) testen.", }) return out