diff --git a/consent-tester/services/banner_text_checker.py b/consent-tester/services/banner_text_checker.py index c6977def..31602f61 100644 --- a/consent-tester/services/banner_text_checker.py +++ b/consent-tester/services/banner_text_checker.py @@ -196,6 +196,33 @@ async def check_banner_text(page) -> dict: legal_ref="EDPB 5/2020 (Consent) + DSK-OH 2024 (Telemedien)", )) + # P100: Granular-Wahl-Pruefung — "Anpassen"/"Einstellungen"-Button + # im Initial-Banner. Wenn er FEHLT (VW-Pattern), ist die granulare + # Cookie-Wahl erst nach Akzeptanz/Ablehnung moeglich — faktische + # Manipulation Richtung "Alle akzeptieren". EDPB 5/2020 §82. + granular_button_texts = [ + "anpassen", "einstellungen", "cookie-einstellungen", + "cookies verwalten", "manage cookies", "customize", + "weitere optionen", "more options", "settings", + "individuell", "detaillierte einstellungen", + "praeferenzen", "preferences", + ] + has_granular_button = any(t in banner_lower for t in granular_button_texts) + if not has_granular_button: + violations.append(Violation( + service="Cookie-Banner", + severity="HIGH", + text="Granulare Cookie-Auswahl im Initial-Banner nicht " + "moeglich (kein 'Anpassen'/'Einstellungen'-Button). " + "Nutzer koennen nur 'Alle akzeptieren' oder 'Nur " + "technisch notwendige' waehlen — Detailwahl pro " + "Kategorie erst nach Akzeptanz/Ablehnung. Das ist " + "faktische Manipulation Richtung Pauschal-Akzeptanz.", + legal_ref="EDPB Guidelines 5/2020 §82 (granular consent), " + "§25 Abs. 1 TDDDG, Art. 4(11) DSGVO (informierte " + "Einwilligung)", + )) + # Check 5: Pre-ticked checkboxes (EuGH Planet49) try: pre_checked = await page.evaluate(""" diff --git a/consent-tester/services/dsi_discovery.py b/consent-tester/services/dsi_discovery.py index 5fc37036..72111701 100644 --- a/consent-tester/services/dsi_discovery.py +++ b/consent-tester/services/dsi_discovery.py @@ -506,14 +506,21 @@ async def discover_dsi_documents( ]; for (const sel of selectors) { const el = document.querySelector(sel); - if (el && el.textContent.trim().length > 200) { - return el.textContent.trim(); + if (el) { + // P98: innerText statt textContent — innerText + // respektiert Whitespace zwischen Block-Elementen. + // textContent verkettet HTML-Tabellen-Zellen ohne + // Spaces (VW-Cookie-Tabelle: ~100 Cookie-Namen + // wurden zu einem Klumpen "smartSignals2UiDsmartSignals2sUiD..."). + const txt = (el.innerText || el.textContent || '').trim(); + if (txt.length > 200) return txt; } } // Fallback: full body minus nav/header/footer const body = document.body.cloneNode(true); body.querySelectorAll('nav, header, footer, script, style, [class*="nav"], [class*="sidebar"]').forEach(e => e.remove()); - return body.textContent?.trim() || ''; + // P98: innerText respektiert Whitespace (s.o.) + return (body.innerText || body.textContent || '').trim(); } """) if text and len(text) > 50: