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
@@ -344,6 +344,17 @@ async def run_consent_test(
await ctx_b.close()
# Matrix-Modus (browser_profile gesetzt): die Per-Engine-Summary
# braucht nur Phase A+B (Cookies/Tracking vor Consent + nach
# Ablehnen, Banner-Text-Checks + Screenshot — alle schon erfasst).
# Die teuren Phasen C (Accept) / D-F (Kategorien) / G (Vendor-Detail)
# ueberspringen → ein Multi-Engine-Scan bleibt im HTTP-Zeitbudget
# (sonst 504). Browser sofort schliessen (Semaphore-Speicher).
if browser_profile:
_apply_edge_case_findings(result, url)
await browser.close()
return result
# ── Phase C: After accepting ─────────────────────────
logger.info("Phase C: Accept consent (%s)", banner.provider)
ctx_c = await browser.new_context(**_ctx_base)
@@ -55,10 +55,26 @@ def _extract_dimensions(banner_result: dict) -> dict[str, float]:
before = phases.get("before_consent") or phases.get("before") or {}
after_reject = phases.get("after_reject") or {}
bv = (banner_result.get("banner_checks") or {}).get("violations") or []
pre_cookies = len(before.get("cookies") or [])
rej_cookies = len(after_reject.get("cookies") or [])
pre_consent = max(0.0, 1.0 - min(1.0, pre_cookies / 10.0))
reject_respect = max(0.0, 1.0 - min(1.0, rej_cookies / 5.0))
summary = banner_result.get("summary") or {}
viol = summary.get("violations") or {}
# Pre-Consent: das rechtliche Signal ist nicht-essentielles TRACKING vor
# Consent, NICHT die Cookie-Rohzahl (essentielle inkl. Consent-Cookie sind
# nach § 25 Abs. 2 erlaubt). Fallback auf Rohzahl nur ohne Summary.
if "before_consent" in viol:
pre_track = viol.get("before_consent") or 0
pre_consent = max(0.0, 1.0 - min(1.0, pre_track / 3.0))
else:
pre_cookies = len(before.get("cookies") or [])
pre_consent = max(0.0, 1.0 - min(1.0, pre_cookies / 10.0))
# Reject: bevorzugt das reject_respected-Verdikt (kein Verstoß UND kein
# neuer Tracker), sonst after_reject-Tracking, sonst Cookie-Rohzahl.
if summary.get("reject_respected") is not None:
reject_respect = 1.0 if summary.get("reject_respected") else 0.2
elif "after_reject" in viol:
reject_respect = max(0.0, 1.0 - min(1.0, (viol.get("after_reject") or 0) / 2.0))
else:
rej_cookies = len(after_reject.get("cookies") or [])
reject_respect = max(0.0, 1.0 - min(1.0, rej_cookies / 5.0))
banner_design = max(0.0, 1.0 - min(1.0, len(bv) / 5.0))
return {
"pre_consent": round(pre_consent, 3),