608fb7faf5
Build + Deploy / build-developer-portal (push) Successful in 1m26s
Build + Deploy / build-tts (push) Successful in 1m38s
Build + Deploy / build-document-crawler (push) Successful in 37s
Build + Deploy / build-dsms-gateway (push) Successful in 26s
Build + Deploy / build-dsms-node (push) Successful in 11s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 18s
CI / secret-scan (push) Has been skipped
CI / nodejs-build (push) Successful in 3m7s
CI / dep-audit (push) Has been skipped
Build + Deploy / build-admin-compliance (push) Successful in 2m22s
Build + Deploy / build-backend-compliance (push) Successful in 3m20s
Build + Deploy / build-ai-sdk (push) Successful in 54s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go (push) Failing after 46s
CI / test-python-backend (push) Successful in 45s
CI / test-python-document-crawler (push) Successful in 30s
CI / test-python-dsms-gateway (push) Successful in 27s
CI / validate-canonical-controls (push) Successful in 17s
Build + Deploy / trigger-orca (push) Successful in 3m37s
CI / sbom-scan (push) Has been skipped
1. DSI Discovery fix for direct-URL use case (e.g. example.com/datenschutz):
- Self-extraction: if the URL itself is a DSE page, extract its text
directly from the page body (main/article/content element)
- Remove "datenschutz" from NOISE_TITLES — it's a legitimate doc title
- Fixes safetykon.de/datenschutz returning 0 documents
2. Banner check definitions (36 checks: 6 L1 + 30 L2):
- consent-tester/checks/banner_checks.py with expert-level hints
- EDPB 3/2022, CNIL rulings, EuGH C-673/17, §25 TDDDG references
- check_key maps to existing consent_scanner check codes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
709 lines
34 KiB
Python
709 lines
34 KiB
Python
"""
|
|
Cookie-Banner Compliance Checks — L1/L2 Hierarchy.
|
|
|
|
6 L1 checks (fundamental requirements) with 30 L2 detail checks.
|
|
Each check_key maps to an existing check in banner_text_checker.py
|
|
or banner_advanced_checks.py.
|
|
|
|
Legal references: EDPB Guidelines 3/2022 (Deceptive Design Patterns),
|
|
EDPB Guidelines 05/2020 (Consent), CNIL Leitlinien, EuGH C-673/17
|
|
(Planet49), §25 TDDDG, Art. 5(3) ePrivacy-RL, Art. 7/12/13 DSGVO.
|
|
"""
|
|
|
|
BANNER_CHECKLIST = [
|
|
# =====================================================================
|
|
# L1-1: Banner vorhanden
|
|
# =====================================================================
|
|
{
|
|
"id": "banner_present",
|
|
"label": "Cookie-Banner vorhanden und funktional",
|
|
"level": 1,
|
|
"parent": None,
|
|
"check_key": "banner_detected",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"Wer nicht-essentielle Cookies oder Tracking einsetzt, braucht eine "
|
|
"Einwilligungsabfrage BEVOR der Zugriff auf das Endgeraet erfolgt "
|
|
"(ss25 Abs. 1 TDDDG, Art. 5(3) ePrivacy-RL). Ohne Banner ist jedes "
|
|
"gesetzte Tracking-Cookie rechtswidrig. Ausnahme: Rein technisch "
|
|
"notwendige Cookies (Session-ID, Warenkorb, Load-Balancer) benoetigen "
|
|
"kein Banner (ss25 Abs. 2 TDDDG). Haeufiger Fehler: Website setzt "
|
|
"Google Analytics, hat aber kein Banner — das ist ein Verstoss ab dem "
|
|
"ersten Seitenaufruf."
|
|
),
|
|
},
|
|
# ── L2 unter banner_present ──────────────────────────────────────
|
|
{
|
|
"id": "banner_language_match",
|
|
"label": "Banner-Sprache entspricht Seitensprache",
|
|
"level": 2,
|
|
"parent": "banner_present",
|
|
"check_key": "banner_language_mismatch",
|
|
"severity": "MEDIUM",
|
|
"hint": (
|
|
"Art. 12(1) DSGVO: Informationen muessen in 'klarer und einfacher "
|
|
"Sprache' bereitgestellt werden. Ein englisches Banner auf einer "
|
|
"deutschen Seite (oder umgekehrt) erfuellt dieses Kriterium nicht, "
|
|
"weil Nutzer die Tragweite ihrer Einwilligung nicht verstehen koennen "
|
|
"(ErwGr. 39, 42 DSGVO). Haeufiger Fehler: CMP-Standardsprache ist "
|
|
"Englisch und wurde nie auf die Seitensprache umgestellt. Pruefung: "
|
|
"<html lang='de'> vs. Banner-Text-Sprache vergleichen."
|
|
),
|
|
},
|
|
{
|
|
"id": "banner_provider_identifiable",
|
|
"label": "CMP-Anbieter identifizierbar",
|
|
"level": 2,
|
|
"parent": "banner_present",
|
|
"check_key": "banner_provider_named",
|
|
"severity": "LOW",
|
|
"hint": (
|
|
"Obwohl gesetzlich nicht explizit gefordert, erleichtert die "
|
|
"Identifizierbarkeit des CMP-Anbieters (Cookiebot, OneTrust, "
|
|
"Usercentrics etc.) die Pruefung, ob der Banner korrekt "
|
|
"konfiguriert ist. Viele CMP-Anbieter sind Auftragsverarbeiter "
|
|
"nach Art. 28 DSGVO — in diesem Fall muss ein AV-Vertrag "
|
|
"vorliegen. Haeufiger Fehler: CMP sendet Consent-Signale an "
|
|
"eigene Server ohne AV-Vertrag."
|
|
),
|
|
},
|
|
{
|
|
"id": "banner_visible_on_load",
|
|
"label": "Banner erscheint beim ersten Seitenaufruf",
|
|
"level": 2,
|
|
"parent": "banner_present",
|
|
"check_key": "banner_detected",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"ss25 Abs. 1 TDDDG: Die Einwilligung muss VOR dem Zugriff "
|
|
"auf das Endgeraet eingeholt werden. Wenn der Banner erst nach "
|
|
"Scrollen, nach einer Verzoegerung oder gar nicht erscheint, "
|
|
"aber gleichzeitig Tracking-Scripts geladen werden, liegt ein "
|
|
"Verstoss vor. Haeufiger Fehler: CMP ist installiert, aber "
|
|
"der Banner wird wegen eines JavaScript-Fehlers oder einer "
|
|
"fehlerhaften Geolocation-Einstellung (z.B. 'nur fuer EU-"
|
|
"Nutzer') nicht angezeigt. Auch Caching-Probleme koennen "
|
|
"dazu fuehren, dass der Banner bei wiederholtem Besuch "
|
|
"nicht geladen wird, obwohl kein Consent vorliegt."
|
|
),
|
|
},
|
|
|
|
# =====================================================================
|
|
# L1-2: Wahlmoeglichkeit (Akzeptieren + Ablehnen)
|
|
# =====================================================================
|
|
{
|
|
"id": "banner_choices",
|
|
"label": "Wahlmoeglichkeit (Akzeptieren + Ablehnen)",
|
|
"level": 1,
|
|
"parent": None,
|
|
"check_key": "reject_button_visible",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"EDPB Guidelines 05/2020, Rn. 41-42: Eine gueltige Einwilligung "
|
|
"erfordert eine echte Wahlmoeglichkeit. Der Nutzer muss Cookies "
|
|
"ebenso einfach ablehnen koennen wie annehmen. Die CNIL hat am "
|
|
"31.12.2021 gegen Google (150 Mio. EUR) und Facebook (60 Mio. EUR) "
|
|
"Bussgelder verhaengt, u.a. weil Ablehnung nicht gleichwertig "
|
|
"moeglich war (CNIL SAN-2021-023/024). Ein Banner ohne Ablehnen-"
|
|
"Option ist keine gueltige Einwilligungsabfrage."
|
|
),
|
|
},
|
|
# ── L2 unter banner_choices ──────────────────────────────────────
|
|
{
|
|
"id": "reject_visible",
|
|
"label": "Ablehnen-Button auf erster Ebene sichtbar",
|
|
"level": 2,
|
|
"parent": "banner_choices",
|
|
"check_key": "reject_button_visible",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"ss25 TDDDG i.V.m. EDPB Guidelines 05/2020, Rn. 86: Der "
|
|
"Ablehnen-Button muss auf der ersten Ebene des Banners sichtbar "
|
|
"sein — nicht hinter 'Einstellungen' oder 'Mehr Informationen' "
|
|
"versteckt. Die franzoesische CNIL hat dies in ihrem Google-"
|
|
"Entscheid (SAN-2021-023) explizit geruegt: 'refuser devait etre "
|
|
"aussi facile qu'accepter'. Haeufiger Fehler: Banner zeigt nur "
|
|
"'Akzeptieren' + 'Einstellungen', Ablehnung erst im Untermenue."
|
|
),
|
|
},
|
|
{
|
|
"id": "reject_same_clicks",
|
|
"label": "Gleiche Klickanzahl fuer Akzeptieren und Ablehnen",
|
|
"level": 2,
|
|
"parent": "banner_choices",
|
|
"check_key": "click_count_asymmetry",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"CNIL SAN-2021-023 (Google, 150 Mio. EUR): 'Le refus necessitait "
|
|
"plusieurs clics alors que l'acceptation pouvait se faire en un "
|
|
"seul clic.' Ablehnung und Zustimmung muessen mit identischer "
|
|
"Klickanzahl erreichbar sein. 1 Klick Accept vs. 2+ Klicks Reject "
|
|
"ist ein Verstoss. Auch die oesterreichische DSB hat dies in "
|
|
"Bescheid D155.520 bestaetigt. Haeufiger Fehler: Accept = 1 Klick, "
|
|
"Reject = Einstellungen oeffnen + Toggle deaktivieren + Speichern "
|
|
"= 3 Klicks."
|
|
),
|
|
},
|
|
{
|
|
"id": "reject_same_size",
|
|
"label": "Gleiche Button-Groesse (kein Dark Pattern)",
|
|
"level": 2,
|
|
"parent": "banner_choices",
|
|
"check_key": "dark_pattern_button_size",
|
|
"severity": "MEDIUM",
|
|
"hint": (
|
|
"EDPB Guidelines 3/2022 (Deceptive Design Patterns), Rn. 62: "
|
|
"Akzeptieren und Ablehnen muessen 'gleichwertig praesentiert' "
|
|
"werden. Konkret: Gleiche Schriftgroesse, Buttongroesse und "
|
|
"Farbprominenz. Ein ueberproportional grosser Accept-Button "
|
|
"(area ratio > 2.5x) gegenueber einem kleinen Reject-Link ist "
|
|
"ein klassisches Dark Pattern. Die CNIL hat Google und Meta "
|
|
"u.a. wegen solcher Klick-Asymmetrie bestraft. Pruefung: "
|
|
"Button-Flaeche und Schriftgroesse beider Optionen vergleichen."
|
|
),
|
|
},
|
|
{
|
|
"id": "reject_same_prominence",
|
|
"label": "Gleiche Farbgebung/Kontrast (kein Contrast-Trick)",
|
|
"level": 2,
|
|
"parent": "banner_choices",
|
|
"check_key": "color_contrast_dark_pattern",
|
|
"severity": "MEDIUM",
|
|
"hint": (
|
|
"EDPB Guidelines 3/2022, Rn. 63-65 (Interface Interference): "
|
|
"Wenn der Accept-Button farblich hervorgehoben ist und der "
|
|
"Reject-Button die gleiche Farbe wie der Hintergrund hat "
|
|
"(transparent oder kaum sichtbar), liegt ein Deceptive Design "
|
|
"Pattern vom Typ 'Hidden in Plain Sight' vor. Beispiel: "
|
|
"Gruener Accept-Button vs. grauer Text-Link 'Ablehnen' der "
|
|
"im Hintergrund verschwindet. Pruefung: background-color des "
|
|
"Reject-Buttons darf nicht identisch mit Banner-Hintergrund sein."
|
|
),
|
|
},
|
|
{
|
|
"id": "reject_no_scroll",
|
|
"label": "Ablehnen ohne Scrollen erreichbar",
|
|
"level": 2,
|
|
"parent": "banner_choices",
|
|
"check_key": "nudging_reject_hidden",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"EDPB Guidelines 3/2022, Rn. 48-50 (Hindering): Der Ablehnen-"
|
|
"Button darf nicht ausserhalb des sichtbaren Banner-Bereichs "
|
|
"platziert werden, sodass Nutzer scrollen muessen. Dies ist ein "
|
|
"'Longer than necessary'-Pattern. Der BGH hat in seiner "
|
|
"Planet49-Nachfolgeentscheidung (I ZR 7/16) klargestellt, dass "
|
|
"die Ablehnung 'ohne unzumutbaren Aufwand' moeglich sein muss. "
|
|
"Haeufiger Fehler: Banner mit langem Text, Reject-Button erst "
|
|
"am Ende sichtbar."
|
|
),
|
|
},
|
|
{
|
|
"id": "reject_no_nudging",
|
|
"label": "Keine manipulative Sprache (Stirring/Nudging)",
|
|
"level": 2,
|
|
"parent": "banner_choices",
|
|
"check_key": "stirring_emotional_language",
|
|
"severity": "LOW",
|
|
"hint": (
|
|
"EDPB Guidelines 3/2022, Rn. 55-59 (Emotional Steering): "
|
|
"Formulierungen wie 'eingeschraenkte Funktionen', 'bestmoegliches "
|
|
"Erlebnis' oder 'Website funktioniert moeglicherweise nicht "
|
|
"richtig' erzeugen emotionalen Druck und beeintraechtigen die "
|
|
"Freiwilligkeit der Einwilligung (Art. 7(4) DSGVO). Auch "
|
|
"sogenannte 'Confirmshaming'-Texte auf dem Ablehnen-Button "
|
|
"(z.B. 'Nein, ich moechte kein gutes Erlebnis') sind unzulaessig. "
|
|
"Korrekt: Neutral formulieren, z.B. 'Alle akzeptieren' / "
|
|
"'Nur Notwendige'."
|
|
),
|
|
},
|
|
|
|
# =====================================================================
|
|
# L1-3: Rechtliche Links (Impressum + DSE)
|
|
# =====================================================================
|
|
{
|
|
"id": "banner_legal_links",
|
|
"label": "Rechtliche Links (Impressum + DSE erreichbar)",
|
|
"level": 1,
|
|
"parent": None,
|
|
"check_key": "impressum_link",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"ss5 TMG / ss5 DDG (ab 2025): Das Impressum muss 'leicht erkennbar, "
|
|
"unmittelbar erreichbar und staendig verfuegbar' sein — auch wenn "
|
|
"ein Cookie-Banner die Seite ueberlagert. LG Rostock (Az. 3 O "
|
|
"22/19): Ein ueberlagernder Banner, der das Impressum verdeckt, "
|
|
"ohne selbst einen Link zu enthalten, verstoesst gegen die "
|
|
"Impressumspflicht. Gleichzeitig verlangt Art. 13 DSGVO, dass "
|
|
"die Datenschutzerklaerung VOR der Einwilligung einsehbar ist "
|
|
"(informierte Einwilligung, ErwGr. 42)."
|
|
),
|
|
},
|
|
# ── L2 unter banner_legal_links ──────────────────────────────────
|
|
{
|
|
"id": "impressum_accessible",
|
|
"label": "Impressum trotz Banner-Overlay erreichbar",
|
|
"level": 2,
|
|
"parent": "banner_legal_links",
|
|
"check_key": "impressum_link",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"ss5 TMG / ss5 DDG, LG Rostock Az. 3 O 22/19: Wenn ein modaler "
|
|
"Cookie-Banner die Seite ueberlagert, MUSS ein Impressum-Link "
|
|
"entweder im Banner selbst oder hinter dem Banner sichtbar sein. "
|
|
"Ohne erreichbares Impressum droht eine Abmahnung nach UWG "
|
|
"(ss3a UWG i.V.m. ss5 TMG). Haeufiger Fehler: Banner ueberlagert "
|
|
"den gesamten Viewport, Impressum-Link im Footer ist nicht "
|
|
"anklickbar. Loesung: Impressum-Link direkt in den Banner "
|
|
"integrieren."
|
|
),
|
|
},
|
|
{
|
|
"id": "dse_link_present",
|
|
"label": "Link zur Datenschutzerklaerung im Banner",
|
|
"level": 2,
|
|
"parent": "banner_legal_links",
|
|
"check_key": "dse_link",
|
|
"severity": "MEDIUM",
|
|
"hint": (
|
|
"Art. 13 DSGVO i.V.m. ErwGr. 42: Eine 'informierte Einwilligung' "
|
|
"setzt voraus, dass der Nutzer die Datenschutzinformationen VOR "
|
|
"seiner Entscheidung einsehen kann. Ohne DSE-Link im Banner fehlt "
|
|
"die Informationsgrundlage — die Einwilligung kann unwirksam sein. "
|
|
"Die belgische DPA hat im TCF-Entscheid (21-02-2022) festgestellt, "
|
|
"dass fehlender Zugang zur DSE vor Consent die Einwilligung "
|
|
"nichtig macht. Haeufiger Fehler: DSE-Link nur im Footer, der "
|
|
"vom Banner verdeckt wird."
|
|
),
|
|
},
|
|
{
|
|
"id": "dse_link_own_domain",
|
|
"label": "DSE-Link zeigt auf eigene Domain (nicht Drittanbieter)",
|
|
"level": 2,
|
|
"parent": "banner_legal_links",
|
|
"check_key": "third_party_dse_link",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"Art. 13/14 DSGVO: Jeder Verantwortliche muss eine EIGENE "
|
|
"Datenschutzerklaerung bereitstellen. Ein Verweis auf die DSE "
|
|
"des CMP-Anbieters, des Hosters oder einer Muttergesellschaft "
|
|
"genuegt nicht, wenn der Website-Betreiber selbst Verantwortlicher "
|
|
"ist. Bei gemeinsamer Verantwortlichkeit (Art. 26 DSGVO) muss die "
|
|
"Vereinbarung offengelegt werden. Haeufiger Fehler: Shopify-Shop "
|
|
"verlinkt auf Shopify-DSE statt eigene."
|
|
),
|
|
},
|
|
{
|
|
"id": "dse_readable_before_consent",
|
|
"label": "DSE vor Einwilligung einsehbar (nicht hinter Consent-Gate)",
|
|
"level": 2,
|
|
"parent": "banner_legal_links",
|
|
"check_key": "dse_link",
|
|
"severity": "MEDIUM",
|
|
"hint": (
|
|
"ErwGr. 42 DSGVO: 'Damit die Einwilligung in Kenntnis der "
|
|
"Sachlage erteilt wird, sollte die betroffene Person mindestens "
|
|
"wissen, wer der Verantwortliche ist und fuer welche Zwecke ihre "
|
|
"personenbezogenen Daten verarbeitet werden sollen.' Wenn die DSE-"
|
|
"Seite selbst erst nach Cookie-Akzeptanz ladbar ist (z.B. weil "
|
|
"sie hinter einem Cookie-Wall liegt), ist die Einwilligung nicht "
|
|
"informiert und damit unwirksam. Pruefung: DSE-Link im Banner "
|
|
"muss ohne vorherige Consent-Entscheidung oeffenbar sein."
|
|
),
|
|
},
|
|
|
|
# =====================================================================
|
|
# L1-4: Gueltige Einwilligung (keine Planet49-Verstoesse)
|
|
# =====================================================================
|
|
{
|
|
"id": "banner_consent_valid",
|
|
"label": "Gueltige Einwilligung (DSGVO-konform)",
|
|
"level": 1,
|
|
"parent": None,
|
|
"check_key": "pre_ticked_checkboxes",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"EuGH C-673/17 (Planet49, 01.10.2019): Eine gueltige Einwilligung "
|
|
"erfordert eine 'aktive Handlung' — vorausgefuellte Checkboxen, "
|
|
"Weitersurfen oder Inaktivitaet genuegen nicht. Art. 4(11) DSGVO "
|
|
"definiert Einwilligung als 'freiwillig fuer den bestimmten Fall, "
|
|
"in informierter Weise und unmisstdeutig abgegebene "
|
|
"Willensbekundung'. Jeder dieser vier Bestandteile muss erfuellt "
|
|
"sein — fehlt einer, ist die gesamte Einwilligung unwirksam."
|
|
),
|
|
},
|
|
# ── L2 unter banner_consent_valid ────────────────────────────────
|
|
{
|
|
"id": "no_pre_ticked",
|
|
"label": "Keine vorausgewaehlten Checkboxen (Planet49)",
|
|
"level": 2,
|
|
"parent": "banner_consent_valid",
|
|
"check_key": "pre_ticked_checkboxes",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"EuGH C-673/17 (Planet49), Rn. 52-58: 'Ein voreingestelltes "
|
|
"Ankreuzkaestchen genuegt nicht.' Der BGH hat dies in der "
|
|
"Folgeentscheidung (I ZR 7/16) bestaetigt. Konkret: Checkboxen "
|
|
"fuer Marketing, Analytics oder Drittanbieter-Cookies duerfen "
|
|
"NICHT vorangehakt sein. Nur technisch notwendige Kategorien "
|
|
"(die keiner Einwilligung beduerfen) duerfen vorausgewaehlt und "
|
|
"ausgegraut sein. Haeufiger Fehler: CMP setzt 'Funktionale "
|
|
"Cookies' vorab auf aktiv — wenn diese Kategorie Drittanbieter "
|
|
"enthaelt, ist das ein Planet49-Verstoss."
|
|
),
|
|
},
|
|
{
|
|
"id": "no_wrong_dse_wording",
|
|
"label": "Keine falsche Formulierung ('Zustimmung zur DSE')",
|
|
"level": 2,
|
|
"parent": "banner_consent_valid",
|
|
"check_key": "wrong_dse_consent",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"Art. 13 DSGVO: Die Datenschutzerklaerung ist eine "
|
|
"Informationspflicht des Verantwortlichen — der Nutzer kann sie "
|
|
"nur 'zur Kenntnis nehmen', nicht 'zustimmen' oder 'akzeptieren'. "
|
|
"Formulierungen wie 'Ich stimme der Datenschutzerklaerung zu' "
|
|
"oder 'Ich akzeptiere die Privacy Policy' sind rechtlich falsch "
|
|
"und koennen die Einwilligung anfechtbar machen. Korrekt: 'Ich "
|
|
"habe die Datenschutzinformationen zur Kenntnis genommen.' "
|
|
"Haeufiger Fehler: CMP-Standardtexte verwenden 'agree to privacy "
|
|
"policy' und werden nicht angepasst."
|
|
),
|
|
},
|
|
{
|
|
"id": "no_modal_dismiss",
|
|
"label": "Klick ausserhalb des Banners ist keine Einwilligung",
|
|
"level": 2,
|
|
"parent": "banner_consent_valid",
|
|
"check_key": "non_modal_dismiss",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"EuGH C-673/17 (Planet49), Rn. 49-50: 'Stillschweigen, bereits "
|
|
"angekreuzte Kaestchen oder Untaetigkeit der betroffenen Person "
|
|
"stellen keine Einwilligung dar.' EDPB Guidelines 05/2020, "
|
|
"Rn. 77: Inaktivitaet, Weitersurfen oder Wegklicken eines "
|
|
"Dialogs darf NICHT als Einwilligung gewertet werden. Wenn ein "
|
|
"nicht-modaler Dialog bei Klick auf den Hintergrund verschwindet "
|
|
"und dabei Consent gesetzt wird, ist diese Einwilligung nichtig. "
|
|
"Loesung: Dialog muss modal sein (aria-modal='true'), nur "
|
|
"explizite Button-Klicks zaehlen."
|
|
),
|
|
},
|
|
{
|
|
"id": "no_coupling",
|
|
"label": "Kein Koppelungsverbot-Verstoss (Art. 7(4))",
|
|
"level": 2,
|
|
"parent": "banner_consent_valid",
|
|
"check_key": "registration_consent_coupling",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"Art. 7(4) DSGVO, ErwGr. 43: Wenn die Erfuellung eines Vertrags "
|
|
"(z.B. Registrierung, Kauf) von einer Einwilligung abhaengt, die "
|
|
"fuer die Vertragserfuellung nicht erforderlich ist, besteht ein "
|
|
"Koppelungsverbot-Verstoss. Beispiel: Login-Button erteilt "
|
|
"gleichzeitig Marketing-Einwilligung ohne separate Checkbox. "
|
|
"Die oesterreichische DSB hat in Bescheid 2021-0.586.257 "
|
|
"klargestellt, dass 'bundling' verschiedener Zwecke in einer "
|
|
"einzigen Einwilligung die Freiwilligkeit ausschliesst. "
|
|
"Pruefung: Login-/Registrierungsformulare auf versteckte "
|
|
"Consent-Kopplung pruefen."
|
|
),
|
|
},
|
|
{
|
|
"id": "no_emotional_language",
|
|
"label": "Keine manipulative/emotionale Sprache (Stirring)",
|
|
"level": 2,
|
|
"parent": "banner_consent_valid",
|
|
"check_key": "stirring_emotional_language",
|
|
"severity": "LOW",
|
|
"hint": (
|
|
"EDPB Guidelines 3/2022, Rn. 55-59 (Emotional Steering): "
|
|
"Formulierungen die Angst, Schuldgefuehle oder Verlustangst "
|
|
"erzeugen, beeintraechtigen die Freiwilligkeit nach Art. 7(4) "
|
|
"DSGVO. Typische Muster: 'Ohne Cookies koennen wir Ihnen kein "
|
|
"optimales Erlebnis bieten', 'Einige Funktionen stehen nicht zur "
|
|
"Verfuegung'. Die spanische AEPD hat in Entscheid PS/00543/2021 "
|
|
"emotionale Sprache in Cookie-Bannern als Verstoss gewertet. "
|
|
"Korrekt: Sachliche Beschreibung der Zwecke ohne Wertung."
|
|
),
|
|
},
|
|
{
|
|
"id": "no_false_necessity",
|
|
"label": "Keine falsche Notwendigkeits-Behauptung (Dark Pattern Language)",
|
|
"level": 2,
|
|
"parent": "banner_consent_valid",
|
|
"check_key": "dark_pattern_language",
|
|
"severity": "MEDIUM",
|
|
"hint": (
|
|
"EDPB Guidelines 05/2020, Rn. 70, Art. 7(4) DSGVO: "
|
|
"Formulierungen wie 'Cookies muessen akzeptiert werden', "
|
|
"'Cookies sind erforderlich' oder 'must be downloaded' fuer "
|
|
"nicht-essentielle Cookies suggerieren eine technische "
|
|
"Notwendigkeit, die nicht besteht. Dies untermieniert die "
|
|
"Freiwilligkeit der Einwilligung. Abzugrenzen: Die Aussage "
|
|
"'Technisch notwendige Cookies sind erforderlich fuer den "
|
|
"Betrieb' ist zulaessig — aber nur wenn sie sich klar auf die "
|
|
"essentielle Kategorie bezieht. Haeufiger Fehler: Pauschale "
|
|
"Formulierung 'Alle Cookies sind fuer den Betrieb notwendig' "
|
|
"obwohl Analytics- und Marketing-Cookies enthalten sind."
|
|
),
|
|
},
|
|
{
|
|
"id": "consent_revocable",
|
|
"label": "Einstellungen erneut zugaenglich (Widerruf, Art. 7(3))",
|
|
"level": 2,
|
|
"parent": "banner_consent_valid",
|
|
"check_key": "re_access_settings",
|
|
"severity": "MEDIUM",
|
|
"hint": (
|
|
"Art. 7(3) DSGVO: 'Der Widerruf der Einwilligung muss so einfach "
|
|
"wie die Erteilung der Einwilligung sein.' Konkret muss ein "
|
|
"persistenter Link zu den Cookie-Einstellungen vorhanden sein — "
|
|
"typischerweise im Footer, als schwebendes Icon oder ueber ein "
|
|
"Fingerprint-Symbol. Die DSK-Orientierungshilfe Telemedien "
|
|
"(Dez. 2021) fordert dies explizit. Haeufiger Fehler: Nach "
|
|
"Schliessung des Banners gibt es keinen Weg zurueck zu den "
|
|
"Einstellungen — der Nutzer muesste Cookies manuell loeschen. "
|
|
"Loesung: Permanenter Footer-Link oder CMP-Widget."
|
|
),
|
|
},
|
|
{
|
|
"id": "consent_expiry_13m",
|
|
"label": "Consent-Cookie max. 13 Monate gueltig (CNIL)",
|
|
"level": 2,
|
|
"parent": "banner_consent_valid",
|
|
"check_key": "consent_cookie_expiry_13m",
|
|
"severity": "MEDIUM",
|
|
"hint": (
|
|
"CNIL-Leitlinie (01.10.2020), Art. 5: 'La validite du "
|
|
"consentement est de 13 mois maximum.' Die Consent-Entscheidung "
|
|
"darf maximal 13 Monate (ca. 395 Tage) gespeichert werden — "
|
|
"danach muss der Nutzer erneut gefragt werden. Auch die DSK-"
|
|
"Orientierungshilfe Telemedien empfiehlt dies. Haeufiger Fehler: "
|
|
"CMP-Default ist 12 Monate (365 Tage) — das ist im Rahmen. "
|
|
"Aber manche setzen 2 Jahre oder 'Session' (kein Ablauf). "
|
|
"Pruefung: Consent-Cookie Expiry-Datum vs. aktuelles Datum, "
|
|
"Differenz darf 395 Tage nicht uebersteigen."
|
|
),
|
|
},
|
|
|
|
# =====================================================================
|
|
# L1-5: Keine Vorab-Cookies/Tracking vor Consent
|
|
# =====================================================================
|
|
{
|
|
"id": "banner_pre_consent",
|
|
"label": "Kein Tracking vor Einwilligung (Phase A)",
|
|
"level": 1,
|
|
"parent": None,
|
|
"check_key": "cookies_before_consent",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"ss25 Abs. 1 TDDDG (vormals ss15(3) TMG): Der Zugriff auf das "
|
|
"Endgeraet des Nutzers (Cookie setzen, Script laden, "
|
|
"Fingerprinting) ist NUR zulaessig, wenn der Nutzer zuvor "
|
|
"eingewilligt hat. Tracking vor jeder Banner-Interaktion ist ein "
|
|
"klarer Verstoss. EuGH C-673/17 (Planet49) und BGH (I ZR 7/16) "
|
|
"bestaetigen: Die Einwilligung muss VOR dem Cookie-Setzen "
|
|
"vorliegen. Haeufiger Fehler: Google Tag Manager laedt GA4 "
|
|
"bereits beim Seitenaufruf — GTM selbst ist zulaessig, aber "
|
|
"die darin konfigurierten Tags muessen consent-gesteuert sein."
|
|
),
|
|
},
|
|
# ── L2 unter banner_pre_consent ──────────────────────────────────
|
|
{
|
|
"id": "no_tracking_scripts_before",
|
|
"label": "Keine Tracking-Scripts vor Consent geladen",
|
|
"level": 2,
|
|
"parent": "banner_pre_consent",
|
|
"check_key": "tracking_before_consent",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"ss25 Abs. 1 TDDDG, Art. 5(3) ePrivacy-RL: Auch das blosse "
|
|
"Laden eines Tracking-Scripts (ohne Cookie) ist ein 'Zugriff "
|
|
"auf das Endgeraet', weil dabei Informationen (IP, User-Agent, "
|
|
"Viewport) uebermittelt werden. Dies gilt fuer GA4, Meta Pixel, "
|
|
"TikTok Pixel, LinkedIn Insight Tag etc. Pruefung: Netzwerk-"
|
|
"Requests in Phase A (vor Consent) auf Tracking-Domains pruefen. "
|
|
"Haeufiger Fehler: CMP-Integration ueber Google Tag Manager, "
|
|
"aber Consent Mode nicht korrekt konfiguriert — Tags feuern "
|
|
"trotzdem."
|
|
),
|
|
},
|
|
{
|
|
"id": "no_tracking_cookies_before",
|
|
"label": "Keine Tracking-Cookies vor Consent gesetzt",
|
|
"level": 2,
|
|
"parent": "banner_pre_consent",
|
|
"check_key": "cookies_before_consent",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"EuGH C-673/17 (Planet49), Rn. 61: Die Einwilligung muss 'vor "
|
|
"dem Speichern von Informationen' eingeholt werden. Cookies wie "
|
|
"_ga, _gid, _fbp, _fbc, IDE, _gcl_*, fr, _pin_*, _tt_*, "
|
|
"li_sugr, _hj* duerfen erst NACH expliziter Einwilligung "
|
|
"gesetzt werden. Pruefung: document.cookie in Phase A auf "
|
|
"bekannte Tracking-Cookie-Patterns pruefen. Haeufiger Fehler: "
|
|
"CMP setzt Consent-Default auf 'granted' — dann werden Cookies "
|
|
"sofort gesetzt und erst bei Ablehnung geloescht (zu spaet)."
|
|
),
|
|
},
|
|
{
|
|
"id": "gcm_default_denied",
|
|
"label": "Google Consent Mode Default = denied",
|
|
"level": 2,
|
|
"parent": "banner_pre_consent",
|
|
"check_key": "google_consent_mode_defaults",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"Google Consent Mode v2: Die Standardwerte fuer analytics_storage, "
|
|
"ad_storage, ad_user_data und ad_personalization muessen auf "
|
|
"'denied' stehen, bis der Nutzer explizit einwilligt. Ein Default "
|
|
"von 'granted' bedeutet, dass Google sofort Daten erhebt — das "
|
|
"ist ein Verstoss gegen ss25 TDDDG. Pruefung: gtag('consent', "
|
|
"'default', {...}) im Quelltext suchen und pruefen ob "
|
|
"analytics_storage oder ad_storage auf 'granted' steht. "
|
|
"Haeufiger Fehler: CMP-Plugin nicht korrekt konfiguriert, "
|
|
"Default-Werte werden nicht ueberschrieben."
|
|
),
|
|
},
|
|
{
|
|
"id": "no_facebook_pixel_before",
|
|
"label": "Kein Meta/Facebook Pixel vor Consent",
|
|
"level": 2,
|
|
"parent": "banner_pre_consent",
|
|
"check_key": "tracking_before_consent",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"Das Meta Pixel (ehem. Facebook Pixel) uebermittelt bei jedem "
|
|
"Seitenaufruf personenbezogene Daten (IP, User-Agent, fbp/fbc-"
|
|
"Cookies) an Meta Platforms Ireland Ltd. — einen Empfaenger in "
|
|
"einem Land, das keinen Angemessenheitsbeschluss hat (Meta "
|
|
"Schrems-II-Problematik). OeVGH (W211 2249247-1, 'noyb vs. "
|
|
"Meta'): Uebermittlung an Meta ohne Einwilligung ist rechtswidrig. "
|
|
"Das Pixel darf ERST nach expliziter Einwilligung geladen werden. "
|
|
"Pruefung: Netzwerk-Requests an connect.facebook.net oder "
|
|
"facebook.com/tr in Phase A."
|
|
),
|
|
},
|
|
{
|
|
"id": "no_google_analytics_before",
|
|
"label": "Kein Google Analytics vor Consent",
|
|
"level": 2,
|
|
"parent": "banner_pre_consent",
|
|
"check_key": "tracking_before_consent",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"Mehrere EU-Datenschutzbehoerden haben den Einsatz von Google "
|
|
"Analytics ohne Einwilligung fuer rechtswidrig erklaert: "
|
|
"oesterreichische DSB (Bescheid 2021-0.586.257, 'noyb-Beschwerde'), "
|
|
"franzoesische CNIL (Mise en demeure, Feb. 2022), italienische "
|
|
"GPDP (Provvedimento 9782890, Juni 2022). GA4 darf erst NACH "
|
|
"Consent geladen werden. Auch mit Server-Side-Tagging oder "
|
|
"IP-Anonymisierung bleibt ein personenbezogener Datentransfer "
|
|
"bestehen. Pruefung: Requests an googletagmanager.com oder "
|
|
"google-analytics.com in Phase A."
|
|
),
|
|
},
|
|
|
|
# =====================================================================
|
|
# L1-6: Ablehnung wird respektiert (Phase B)
|
|
# =====================================================================
|
|
{
|
|
"id": "banner_post_reject",
|
|
"label": "Ablehnung wird respektiert (Phase B)",
|
|
"level": 1,
|
|
"parent": None,
|
|
"check_key": "tracking_after_reject",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"ss25 Abs. 1 TDDDG: Wird die Einwilligung verweigert, darf "
|
|
"KEIN nicht-essentieller Zugriff auf das Endgeraet erfolgen. "
|
|
"Tracking, das nach Ablehnung weiterlaeuft, ist ein schwerer "
|
|
"Verstoss — der Nutzer hat seinen Willen ausdruecklich erklaert. "
|
|
"Die franzoesische CNIL hat in mehreren Entscheiden (u.a. "
|
|
"SAN-2022-009, Criteo, 40 Mio. EUR) geruegt, dass Tracking "
|
|
"trotz Ablehnung fortgesetzt wurde. Pruefung: Phase A vs. "
|
|
"Phase B vergleichen — neue Tracking-Scripts oder Cookies "
|
|
"nach Reject sind ein Verstoss."
|
|
),
|
|
},
|
|
# ── L2 unter banner_post_reject ──────────────────────────────────
|
|
{
|
|
"id": "tracking_stops_after_reject",
|
|
"label": "Tracking-Scripts werden nach Ablehnung entfernt",
|
|
"level": 2,
|
|
"parent": "banner_post_reject",
|
|
"check_key": "tracking_after_reject",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"Nach Ablehnung duerfen keine neuen Tracking-Scripts geladen "
|
|
"werden. Bestehende Scripts sollten deaktiviert oder entfernt "
|
|
"werden. ss25 TDDDG kennt keinen 'Bestandsschutz' fuer bereits "
|
|
"geladene Tracker — auch wenn ein Script in Phase A (vor Consent) "
|
|
"fehlerhaft geladen wurde, muss es nach Reject gestoppt werden. "
|
|
"Pruefung: Netzwerk-Requests in Phase B auf neue Tracking-Domains "
|
|
"pruefen. Haeufiger Fehler: GA4 Enhanced Measurement sendet "
|
|
"weiterhin scroll/outbound-Events trotz Ablehnung, weil der "
|
|
"Consent Mode 'update'-Befehl nicht korrekt feuert."
|
|
),
|
|
},
|
|
{
|
|
"id": "cookies_removed_after_reject",
|
|
"label": "Tracking-Cookies nach Ablehnung entfernt",
|
|
"level": 2,
|
|
"parent": "banner_post_reject",
|
|
"check_key": "tracking_after_reject",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"Wenn in Phase A faelschlicherweise Tracking-Cookies gesetzt "
|
|
"wurden, muessen diese nach Ablehnung geloescht werden. Ein CMP "
|
|
"sollte bei Reject ein 'Cookie-Cleanup' durchfuehren: _ga, _gid, "
|
|
"_fbp etc. entfernen. Die CNIL-Leitlinie (Okt. 2020), Rn. 23 "
|
|
"verlangt, dass 'der Verantwortliche sicherstellt, dass die "
|
|
"Ablehnung effektiv umgesetzt wird'. Pruefung: Cookie-Liste vor "
|
|
"und nach Reject vergleichen — Tracking-Cookies sollten "
|
|
"verschwunden sein."
|
|
),
|
|
},
|
|
{
|
|
"id": "no_new_tracking_after_reject",
|
|
"label": "Keine neuen Tracker nach Ablehnung",
|
|
"level": 2,
|
|
"parent": "banner_post_reject",
|
|
"check_key": "tracking_after_reject",
|
|
"severity": "CRITICAL",
|
|
"hint": (
|
|
"Der gravierendste Verstoss: NACH expliziter Ablehnung werden "
|
|
"NEUE Tracking-Services geladen, die vorher nicht aktiv waren. "
|
|
"Dies kann passieren, wenn das CMP den Reject-Status nicht "
|
|
"korrekt an den Tag Manager weitergibt oder wenn hardcoded "
|
|
"Scripts im HTML stehen, die nicht consent-gesteuert sind. "
|
|
"CNIL SAN-2022-009 (Criteo, 40 Mio. EUR): 'Des traceurs "
|
|
"continuaient d'etre deposés malgre le refus de l'utilisateur.' "
|
|
"Pruefung: Diff der Tracking-Services zwischen Phase A und "
|
|
"Phase B — neue Eintraege sind ein Verstoss."
|
|
),
|
|
},
|
|
{
|
|
"id": "site_functional_after_reject",
|
|
"label": "Seite bleibt nutzbar nach Ablehnung (kein Cookie-Wall)",
|
|
"level": 2,
|
|
"parent": "banner_post_reject",
|
|
"check_key": "cookie_wall",
|
|
"severity": "HIGH",
|
|
"hint": (
|
|
"EDPB Guidelines 05/2020, Rn. 39: 'Access to services and "
|
|
"functionalities must not be made conditional on the consent "
|
|
"of a user to the storing of information.' Eine sogenannte "
|
|
"'Cookie Wall', die den Zugang zur Website nach Ablehnung "
|
|
"vollstaendig blockiert, macht die Einwilligung unfreiwillig. "
|
|
"Ausnahme: 'Consent or Pay'-Modelle (EDPB Opinion 08/2024) "
|
|
"sind unter engen Bedingungen zulaessig — dafuer muss die "
|
|
"Bezahlalternative 'angemessen' sein und darf nicht "
|
|
"ueberzogen sein. Haeufiger Fehler: Website zeigt nach "
|
|
"Ablehnung eine leere Seite oder Redirect auf Fehlerseite."
|
|
),
|
|
},
|
|
]
|