fix(browser-matrix): Tracking-Signal statt Cookie-Rohzahl + Matrix-Schnellpfad

Korrektheit (§ 25 TDDDG): "Cookies vor Consent" ist KEIN Verstoss per se —
technisch notwendige Cookies inkl. des Consent-Cookies (speichert die
Ablehnung) sind nach Abs. 2 erlaubt. Verstoss ist nur nicht-essentielles
TRACKING vor Consent.
- browser_cross_finding: Befund haengt jetzt an violations.before_consent
  (Tracking), nicht an der Cookie-Rohzahl; § 25 Abs. 2-Hinweis im Detail.
  Regressionstest: Cookies-ohne-Tracking → KEIN Befund.
- multi_browser_scanner._extract_dimensions: Score nutzt Tracking-Violations
  + reject_respected-Verdikt statt Rohzahl (Fallback erhalten).
- BrowserBehaviorView: "Cookies vor Consent" nur rot/⚠ bei Tracking,
  "nach Ablehnen" neutral (Verdikt = reject-Spalte); erklaerende Zeile.

Speed: run_consent_test ueberspringt im Matrix-Modus (browser_profile gesetzt)
die teuren Phasen C/D-F/G — nur A+B noetig. Verhindert das 504 beim
Multi-Engine-Scan (BMW 4 Engines lief sonst in den 338s-Gateway-Timeout).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-13 00:10:41 +02:00
parent fa8ad030cb
commit 3f90e40807
5 changed files with 99 additions and 34 deletions
@@ -58,34 +58,44 @@ def build_cross_findings(matrix: dict | None) -> list[dict]:
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]
def _pre_track(r: dict) -> int:
# Nicht-essentielles TRACKING vor Consent (§ 25 Abs. 1 TDDDG) — das
# rechtlich relevante Signal. NICHT die Cookie-Rohzahl: die enthaelt
# technisch notwendige Cookies inkl. des Consent-Cookies selbst (das
# speichern MUSS, dass abgelehnt wurde) → § 25 Abs. 2, einwilligungsfrei.
return (_s(r).get("violations") or {}).get("before_consent") or 0
track_yes = [r for r in data if _pre_track(r) > 0]
track_no = [r for r in data if _pre_track(r) == 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:
# ── Tracking VOR der Einwilligung (nicht: jede Cookie-Rohzahl) ────────
_ESS = (" Technisch notwendige Cookies inkl. des Consent-Cookies sind "
"ausgenommen (§ 25 Abs. 2 TDDDG).")
if track_yes and not track_no:
out.append({
"title": "Cookies vor der Einwilligung — in allen Browsern",
"detail": "In jeder getesteten Engine werden vor einer aktiven "
"Einwilligung Cookies gesetzt.",
"title": "Tracking vor der Einwilligung — in allen Browsern",
"detail": "In jeder getesteten Engine feuern vor einer aktiven "
"Einwilligung nicht-essentielle Tracker." + _ESS,
"severity": "HIGH",
"affected": _labels(pre_yes),
"measure": "Tracking-/Marketing-Cookies erst nach aktiver "
"Einwilligung setzen (§ 25 Abs. 1 TDDDG).",
"affected": _labels(track_yes),
"measure": "Tracking-/Marketing-Skripte erst nach aktiver "
"Einwilligung laden (§ 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 "
elif track_yes and track_no:
masked = sorted({_protection_label(r) for r in track_no if _protection_label(r)})
hint = (f" Die unauffälligen Engines ({', '.join(_labels(track_no))}) "
f"unterdrücken die Tracker 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}",
"title": "Tracking vor Einwilligung — nur in manchen Browsern",
"detail": f"Nicht-essentielle Tracker vor Consent in "
f"{', '.join(_labels(track_yes))}, nicht in "
f"{', '.join(_labels(track_no))}.{hint}",
"severity": "HIGH",
"affected": _labels(pre_yes),
"affected": _labels(track_yes),
"measure": "Server-/skriptseitig auf Consent gaten statt auf den "
"Tracking-Schutz einzelner Browser zu vertrauen.",
})
@@ -8,7 +8,7 @@ from compliance.services.browser_cross_finding import build_cross_findings
def _row(pid, label, engine, *, before=0, reject_ok=True,
impressum=True, dse=True, with_summary=True):
impressum=True, dse=True, with_summary=True, track_before=0):
if not with_summary:
return {"profile_id": pid, "label": label, "engine": engine,
"summary": None, "error": "launch failed"}
@@ -19,6 +19,9 @@ def _row(pid, label, engine, *, before=0, reject_ok=True,
"cookies_after_reject": 0 if reject_ok else 2,
"reject_respected": reject_ok,
"surface": {"has_impressum_link": impressum, "has_dse_link": dse},
# before_consent = nicht-essentielles Tracking vor Consent (das
# rechtlich relevante Signal, NICHT die Cookie-Rohzahl `before`).
"violations": {"before_consent": track_before},
},
}
@@ -32,22 +35,34 @@ def test_empty_matrix():
assert build_cross_findings({"browser_matrix": []}) == []
def test_pre_consent_in_all_engines_high():
def test_tracking_before_consent_in_all_engines_high():
# Nicht-essentielles Tracking vor Consent (track_before>0) in allen Engines.
m = {"browser_matrix": [
_row("chromium-headed-de", "Chromium", "blink", before=5),
_row("firefox-headed-de", "Firefox", "gecko", before=3),
_row("chromium-headed-de", "Chromium", "blink", before=5, track_before=2),
_row("firefox-headed-de", "Firefox", "gecko", before=3, track_before=1),
]}
f = build_cross_findings(m)
hit = [x for x in f if "in allen Browsern" in x["title"]]
hit = [x for x in f if "Tracking vor der Einwilligung — in allen" in x["title"]]
assert hit and hit[0]["severity"] == "HIGH"
assert "TDDDG" in hit[0]["measure"]
assert "§ 25 Abs. 2" in hit[0]["detail"] # essentielle Cookies ausgenommen
def test_pre_consent_inconsistent_flags_browser_protection():
# Chrome (nachgiebig) setzt vor Consent, Safari/WebKit (ITP) nicht.
def test_cookies_before_consent_without_tracking_is_no_finding():
# KERN-REGRESSION (User-Frage): Cookies vor Consent vorhanden, aber KEIN
# Tracking (z.B. nur das Consent-Cookie selbst) → KEIN Verstoß-Befund.
m = {"browser_matrix": [
_row("chrome-channel-desktop-de", "Chrome", "blink", before=4),
_row("webkit-headed-de", "Safari/WebKit", "webkit", before=0),
_row("chromium-headed-de", "Chromium", "blink", before=5, track_before=0),
_row("firefox-headed-de", "Firefox", "gecko", before=4, track_before=0),
]}
f = build_cross_findings(m)
assert [x for x in f if "Tracking vor" in x["title"]] == []
def test_tracking_before_consent_inconsistent_flags_browser_protection():
# Chrome (nachgiebig) lässt Tracker vor Consent, Safari/WebKit (ITP) nicht.
m = {"browser_matrix": [
_row("chrome-channel-desktop-de", "Chrome", "blink", before=4, track_before=3),
_row("webkit-headed-de", "Safari/WebKit", "webkit", before=0, track_before=0),
]}
f = build_cross_findings(m)
hit = [x for x in f if "nur in manchen" in x["title"]]