feat(cookie): Finding 'vague_duration' — unkonkrete Speicherdauer
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 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ const TYPE_LABEL: Record<string, string> = {
|
|||||||
tracker_as_necessary: 'Tracker als „notwendig" deklariert',
|
tracker_as_necessary: 'Tracker als „notwendig" deklariert',
|
||||||
missing_purpose: 'Zweck fehlt',
|
missing_purpose: 'Zweck fehlt',
|
||||||
excessive_lifetime: 'Speicherdauer zu lang',
|
excessive_lifetime: 'Speicherdauer zu lang',
|
||||||
|
vague_duration: 'Speicherdauer nicht konkret',
|
||||||
third_country: 'Drittland-Transfer',
|
third_country: 'Drittland-Transfer',
|
||||||
eu_alternative: 'EU-Alternative verfügbar',
|
eu_alternative: 'EU-Alternative verfügbar',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,31 @@ _EEA = {
|
|||||||
_SEV_ORDER = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
|
_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:
|
def _duration_days(s: str) -> int:
|
||||||
"""Grobe Normalisierung einer Laufzeit-Angabe in Tage (0 = Session)."""
|
"""Grobe Normalisierung einer Laufzeit-Angabe in Tage (0 = Session)."""
|
||||||
s = (s or "").lower()
|
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 []:
|
for c in v.get("cookies") or []:
|
||||||
checked += 1
|
checked += 1
|
||||||
name = c.get("name", "")
|
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 {}
|
rich = lookup_cookie(name) or {}
|
||||||
big = big_lib.get(name.lower(), {})
|
big = big_lib.get(name.lower(), {})
|
||||||
if not rich and not big:
|
if not rich and not big:
|
||||||
|
|||||||
@@ -79,6 +79,24 @@ def test_excessive_lifetime():
|
|||||||
assert el and "Art. 5" in el[0]["remediation"]
|
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():
|
def test_big_library_covers_cookie_not_in_rich_db():
|
||||||
# Cookie nicht in der 35er rich-DB, aber in der grossen 2287er (big_lib).
|
# Cookie nicht in der 35er rich-DB, aber in der grossen 2287er (big_lib).
|
||||||
big = {"bmw_track_de": {
|
big = {"bmw_track_de": {
|
||||||
|
|||||||
Reference in New Issue
Block a user