feat(consent-tester): 3 Edge-Cases — kein-Banner-konform, Geo-Caveat, Non-Cookie-Tracking
#1/#2: Wenn KEIN Banner erkannt UND kein Tracking vor Consent (statische Seite oder nur technisch notwendige Cookies, §25 Abs.2 TDDDG) → affirmativer LOW-Befund "konform, kein Banner nötig" statt stillem "Banner fehlt". Inkl. Geo-Caveat (Scan außerhalb EU sieht geo-getargetete Banner evtl. nicht). #3: detect_non_cookie_tracking erkennt Pixel/Fingerprinting per Domain-Signatur (Meta, TikTok, LinkedIn, Pinterest, Clarity, FingerprintJS, Hotjar, Reddit, Snapchat) → MEDIUM-Befund "§25/Art.5(3) gilt auch ohne Cookies". '0 Cookies' ≠ 'kein einwilligungspflichtiges Tracking'. Verdrahtet in consent_scanner vor dem Return. Tests + py_compile grün. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,76 @@ def _cookieless_finding(has_dse: bool) -> Violation:
|
||||
)
|
||||
|
||||
|
||||
# ── #1/#2: Kein Banner erkannt → affirmativ einordnen (nicht still) ─────
|
||||
def build_no_banner_finding(has_dse: bool) -> Violation:
|
||||
"""Wenn KEIN Banner erkannt wurde UND vor Consent kein einwilligungs-
|
||||
pflichtiges Tracking lief: kein Banner noetig (statische Seite / nur
|
||||
technisch notwendige Cookies, §25 Abs.2 TDDDG) → konform. Inkl. Geo-Caveat.
|
||||
Nur aufrufen, wenn before_violations UND before_tracking leer sind."""
|
||||
return Violation(
|
||||
service="Cookie-Banner",
|
||||
severity="LOW",
|
||||
text=(
|
||||
"Kein Consent-Banner erkannt — und es ist auch keiner erforderlich: "
|
||||
"Vor einer Einwilligung wurde kein einwilligungspflichtiges Tracking "
|
||||
"geladen (keine Tracking-Skripte/-Pixel). Typisch fuer eine statische "
|
||||
"Seite oder eine Seite mit ausschliesslich technisch notwendigen Cookies "
|
||||
"(Session, CSRF, Login, Warenkorb, Sprache) — die nach §25 Abs.2 TDDDG / "
|
||||
"Art.5(3) ePrivacy KEINE Einwilligung brauchen. Bewertung: konform. Es "
|
||||
"bleibt nur die Informationspflicht — eine Datenschutzerklaerung muss "
|
||||
"erreichbar sein"
|
||||
+ ("." if has_dse else " (DSE-Link wurde hier nicht gefunden — bitte pruefen).")
|
||||
+ " METHODEN-HINWEIS: Falls die Seite Geo-Targeting nutzt, sieht ein Scan "
|
||||
"ausserhalb der EU evtl. keinen Banner — den Scanner-Standort (EU-IP) bei "
|
||||
"diesem Befund mitdenken."
|
||||
),
|
||||
legal_ref="§25 Abs. 2 TDDDG (Ausnahme technisch notwendig), Art. 5(3) ePrivacy, "
|
||||
"EDPB Guidelines 2/2023",
|
||||
)
|
||||
|
||||
|
||||
# ── #3: Non-Cookie-Tracking (Pixel/Fingerprinting) — §25 gilt auch ohne Cookies ──
|
||||
# Domain-Signaturen, die auf Script-URLs zuverlaessig matchen.
|
||||
_NON_COOKIE_TRACKERS = {
|
||||
"Meta-Pixel (Facebook)": ("connect.facebook.net", "facebook.com/tr"),
|
||||
"TikTok-Pixel": ("analytics.tiktok.com",),
|
||||
"LinkedIn Insight Tag": ("snap.licdn.com",),
|
||||
"Pinterest-Tag": ("s.pinimg.com/ct",),
|
||||
"Microsoft Clarity": ("clarity.ms",),
|
||||
"Fingerprinting (FingerprintJS)": ("fingerprintjs", "fpjs.io", "fpcdn.io"),
|
||||
"Hotjar": ("static.hotjar.com",),
|
||||
"Reddit-Pixel": ("redditstatic.com/ads",),
|
||||
"Snapchat-Pixel": ("sc-static.net", "tr.snapchat.com"),
|
||||
}
|
||||
|
||||
|
||||
def detect_non_cookie_tracking(scripts: list) -> list:
|
||||
"""Erkennt cookieloses/script-basiertes Tracking (Pixel/Fingerprinting) in
|
||||
den geladenen Skript-URLs. Pure + testbar. §25 Abs.1 TDDDG/Art.5(3) ist
|
||||
technologieneutral → gilt auch ohne Cookies."""
|
||||
blob = " ".join(s.lower() for s in (scripts or []) if s)
|
||||
return [name for name, sigs in _NON_COOKIE_TRACKERS.items()
|
||||
if any(sig in blob for sig in sigs)]
|
||||
|
||||
|
||||
def build_non_cookie_tracking_finding(detected: list) -> Violation:
|
||||
return Violation(
|
||||
service="Cookie-Banner",
|
||||
severity="MEDIUM",
|
||||
text=(
|
||||
"Non-Cookie-Tracking erkannt: " + ", ".join(detected) + ". "
|
||||
"§25 Abs.1 TDDDG / Art.5(3) ePrivacy ist technologieneutral — die "
|
||||
"Einwilligungspflicht gilt AUCH ohne Cookies (Pixel, Web-Beacons, "
|
||||
"Fingerprinting). Ein reiner Cookie-Check uebersieht das: '0 Cookies' "
|
||||
"heisst NICHT 'kein einwilligungspflichtiges Tracking'. Diese Techniken "
|
||||
"vor jeder Einwilligung pruefen. (Hinweis: Google Consent Mode v2 ist "
|
||||
"KEINE gueltige Einwilligung, sondern sendet nur modellierte Signale.)"
|
||||
),
|
||||
legal_ref="§25 Abs.1 TDDDG, Art.5(3) ePrivacy, EDPB Guidelines 2/2023 "
|
||||
"(Pixel/Fingerprinting/URL-Tracking einwilligungspflichtig)",
|
||||
)
|
||||
|
||||
|
||||
async def check_banner_text(page) -> dict:
|
||||
"""Check cookie banner text for legal issues.
|
||||
|
||||
|
||||
@@ -540,6 +540,28 @@ async def run_consent_test(
|
||||
result.banner_provider, len(result.before_violations), len(result.reject_violations),
|
||||
len(result.category_tests), len(result.cmp_payloads),
|
||||
)
|
||||
|
||||
# Edge-Cases: kein Banner affirmativ einordnen (#1/#2) + Non-Cookie-Tracking (#3).
|
||||
try:
|
||||
from services.banner_text_checker import (
|
||||
build_no_banner_finding, detect_non_cookie_tracking,
|
||||
build_non_cookie_tracking_finding,
|
||||
)
|
||||
# #1/#2: KEIN Banner + KEIN Tracking vor Consent → konform (statisch /
|
||||
# nur technisch notwendig), nicht still "Banner fehlt". Inkl. Geo-Caveat.
|
||||
if (not result.banner_detected and not result.before_violations
|
||||
and not result.before_tracking):
|
||||
result.banner_text_violations.append(
|
||||
build_no_banner_finding(result.banner_has_dse_link))
|
||||
# #3: Pixel/Fingerprinting (cookieloses Tracking) → §25 gilt auch ohne Cookies.
|
||||
_nct = detect_non_cookie_tracking(
|
||||
(result.before_scripts or []) + (result.accept_scripts or []))
|
||||
if _nct:
|
||||
result.banner_text_violations.append(
|
||||
build_non_cookie_tracking_finding(_nct))
|
||||
except Exception as e:
|
||||
logger.warning("Edge-case findings skipped: %s", e)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,11 @@ statt Consent-Banner (z.B. bayshore.ai). Standard-Opt-in-Checks duerfen dann
|
||||
NICHT feuern (sonst False Positives).
|
||||
"""
|
||||
|
||||
from services.banner_text_checker import is_cookieless_optout
|
||||
from services.banner_text_checker import (
|
||||
is_cookieless_optout,
|
||||
detect_non_cookie_tracking,
|
||||
build_no_banner_finding,
|
||||
)
|
||||
|
||||
|
||||
def test_bayshore_cookieless_optout_detected():
|
||||
@@ -32,3 +36,35 @@ def test_signal_without_optout_word_is_not_detected():
|
||||
|
||||
def test_empty():
|
||||
assert not is_cookieless_optout("")
|
||||
|
||||
|
||||
# ── #3: Non-Cookie-Tracking-Erkennung ──────────────────────────────────
|
||||
def test_detect_meta_pixel():
|
||||
assert detect_non_cookie_tracking(
|
||||
["https://connect.facebook.net/en_US/fbevents.js"]) == ["Meta-Pixel (Facebook)"]
|
||||
|
||||
|
||||
def test_detect_clarity_and_fingerprint():
|
||||
found = detect_non_cookie_tracking([
|
||||
"https://www.clarity.ms/tag/abc", "https://cdn.fpjs.io/v3/x.js"])
|
||||
assert "Microsoft Clarity" in found
|
||||
assert "Fingerprinting (FingerprintJS)" in found
|
||||
|
||||
|
||||
def test_detect_none_on_plain_scripts():
|
||||
assert detect_non_cookie_tracking(
|
||||
["https://example.com/app.js", "/static/main.css"]) == []
|
||||
assert detect_non_cookie_tracking([]) == []
|
||||
|
||||
|
||||
# ── #1/#2: Kein-Banner-affirmativ-Befund ───────────────────────────────
|
||||
def test_no_banner_finding_is_low_and_compliant():
|
||||
v = build_no_banner_finding(has_dse=True)
|
||||
assert v.severity == "LOW"
|
||||
assert "konform" in v.text.lower()
|
||||
assert "geo-targeting" in v.text.lower() # Geo-Caveat enthalten
|
||||
|
||||
|
||||
def test_no_banner_finding_flags_missing_dse():
|
||||
v = build_no_banner_finding(has_dse=False)
|
||||
assert "dse" in v.text.lower() or "datenschutzerkl" in v.text.lower()
|
||||
|
||||
Reference in New Issue
Block a user