From 4c45f11e43f824f8ccc60e4c0bda91a2bd400fc8 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 11 Jun 2026 08:33:06 +0200 Subject: [PATCH] =?UTF-8?q?feat(cookie):=20Finding=20'vague=5Fduration'=20?= =?UTF-8?q?=E2=80=94=20unkonkrete=20Speicherdauer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flaggt Laufzeit-Angaben ohne konkrete Dauer/Kriterium ('dauerhaft', 'bis zur Loeschung', 'bis Nutzer deaktiviert', 'unbegrenzt' …) — Art. 5(1)(e) + Art. 13 DSGVO. Library-unabhaengig, gilt fuer ALLE Cookies (Coverage auf BMWs 780). '13 Monate'/'Session'/'bis Widerruf, max. X' bleiben ok. Co-Authored-By: Claude Opus 4.7 --- .../agent/_components/CookieLibraryPanel.tsx | 1 + .../services/cookie_library_check.py | 39 +++++++++++++++++++ .../tests/test_cookie_library_check.py | 18 +++++++++ 3 files changed, 58 insertions(+) diff --git a/admin-compliance/app/sdk/agent/_components/CookieLibraryPanel.tsx b/admin-compliance/app/sdk/agent/_components/CookieLibraryPanel.tsx index e9d709b6..2848a22c 100644 --- a/admin-compliance/app/sdk/agent/_components/CookieLibraryPanel.tsx +++ b/admin-compliance/app/sdk/agent/_components/CookieLibraryPanel.tsx @@ -32,6 +32,7 @@ const TYPE_LABEL: Record = { tracker_as_necessary: 'Tracker als „notwendig" deklariert', missing_purpose: 'Zweck fehlt', excessive_lifetime: 'Speicherdauer zu lang', + vague_duration: 'Speicherdauer nicht konkret', third_country: 'Drittland-Transfer', eu_alternative: 'EU-Alternative verfügbar', } diff --git a/backend-compliance/compliance/services/cookie_library_check.py b/backend-compliance/compliance/services/cookie_library_check.py index 157d3796..89842fe0 100644 --- a/backend-compliance/compliance/services/cookie_library_check.py +++ b/backend-compliance/compliance/services/cookie_library_check.py @@ -53,6 +53,31 @@ _EEA = { _SEV_ORDER = {"HIGH": 0, "MEDIUM": 1, "LOW": 2} +# Vage Laufzeit-Formulierungen — keine konkrete Speicherdauer i.S.v. +# Art. 5(1)(e) + Art. 13 DSGVO (User-Domain-Vorgabe 2026-06-11). +_VAGUE_DURATION = ( + "dauerhaft", "bis zur löschung", "bis zur loeschung", "deaktiviert", + "unbegrenzt", "unbefristet", "solange erforderlich", "solange benötigt", + "unendlich", "permanent", "auf unbestimmte zeit", "kein ablauf", + "no expir", "persistent", "until deleted", +) + + +def _is_vague_duration(expiry: str) -> bool: + """True, wenn die Angabe vage ist (keine konkrete Dauer/Session/Kriterium).""" + e = (expiry or "").strip().lower() + if not e: + return False + has_concrete = ( + bool(re.search(r"\d+\s*(tag|woche|monat|jahr|stunde|minute|day|week|" + r"month|year|hour|min)", e)) + or "session" in e + or "browser schließ" in e or "browser schliess" in e + or "schließen des browser" in e or "schliessen des browser" in e + ) + return not has_concrete and any(p in e for p in _VAGUE_DURATION) + + def _duration_days(s: str) -> int: """Grobe Normalisierung einer Laufzeit-Angabe in Tage (0 = Session).""" s = (s or "").lower() @@ -91,6 +116,20 @@ def analyze_cookies(vendors: list[dict], big_lib: dict | None = None) -> dict: for c in v.get("cookies") or []: checked += 1 name = c.get("name", "") + # Vage Speicherdauer — OHNE Library, gilt fuer ALLE Cookies. + if _is_vague_duration(c.get("expiry", "")): + findings.append({ + "vendor": vname, "cookie": name, "type": "vague_duration", + "severity": "MEDIUM", "declared": c.get("expiry", ""), + "library_purpose": "", + "remediation": ( + f"Speicherdauer von '{name}' ist nicht konkret angegeben " + f"('{c.get('expiry', '')}'). Art. 5 Abs. 1 lit. e + Art. 13 " + f"DSGVO verlangen eine konkrete Dauer oder nachvollziehbare " + f"Kriterien (z.B. '13 Monate', 'Session', 'bis Widerruf, " + f"max. 13 Monate')." + ), + }) rich = lookup_cookie(name) or {} big = big_lib.get(name.lower(), {}) if not rich and not big: diff --git a/backend-compliance/compliance/tests/test_cookie_library_check.py b/backend-compliance/compliance/tests/test_cookie_library_check.py index 9b015a01..da585dbc 100644 --- a/backend-compliance/compliance/tests/test_cookie_library_check.py +++ b/backend-compliance/compliance/tests/test_cookie_library_check.py @@ -79,6 +79,24 @@ def test_excessive_lifetime(): assert el and "Art. 5" in el[0]["remediation"] +def test_vague_duration_flagged_concrete_ok(): + # User-Beispiel Salesforce: "bis der Nutzer es deaktiviert" = vage. + out = analyze_cookies([{ + "name": "Salesforce", "category": "necessary", + "cookies": [ + {"name": "MUTEX_X", "purpose": "x", + "expiry": "Wird solange gespeichert, bis es durch den Nutzer in seinem Browser deaktiviert wird."}, + {"name": "ok1", "purpose": "x", "expiry": "13 Monate"}, + {"name": "sess", "purpose": "x", + "expiry": "Dieses Session Cookie wird beim Schließen des Browsers wieder gelöscht."}, + ], + }]) + vd = [f for f in out["findings"] if f["type"] == "vague_duration"] + assert len(vd) == 1 # nur MUTEX_X; 13 Monate + Session ok + assert vd[0]["cookie"] == "MUTEX_X" + assert "Art. 5" in vd[0]["remediation"] + + def test_big_library_covers_cookie_not_in_rich_db(): # Cookie nicht in der 35er rich-DB, aber in der grossen 2287er (big_lib). big = {"bmw_track_de": {