feat(cookie): missing_retention — Vendor ohne Speicherdauer/Löschfrist

Vendor-Ebenen-Finding: greift, wenn ein Vendor eine Verarbeitung deklariert
(Kategorie/Zweck), aber KEINE Cookies gelistet sind UND keine persistence
angegeben ist (z.B. Nayoki GmbH — 'necessary' Auftragsverarbeiter ohne
Löschfrist). Die Pro-Cookie-Schleife sah solche Vendors nie (0 Cookies →
0 Findings). Remediation = Ticket-Text 'bitte Löschfrist festlegen'.
Art. 5 Abs. 1 lit. e + Art. 13 Abs. 2 lit. a → Control AUTH-2051-A03.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-11 10:02:59 +02:00
parent 32ba8d16b1
commit 7fa9968ce1
3 changed files with 53 additions and 0 deletions
@@ -8,6 +8,9 @@ Befund-Typen:
tracker_as_necessary — als notwendig deklariert, laut Library kein techn. Zweck
missing_purpose — kein Zweck deklariert, Library kennt ihn
excessive_lifetime — deklarierte Speicherdauer >> typische (Art. 5(1)(e))
vague_duration — Speicherdauer nicht konkret (Art. 5(1)(e)+13) [je Cookie]
missing_retention — Verarbeitung deklariert, aber keine Speicherdauer/
Löschfrist + keine Cookies gelistet [je Vendor]
third_country — Drittland-Transfer (Schrems II, Art. 44 ff.) [je Vendor]
eu_alternative — EU-Ersatz verfügbar (kommerziell) [je Vendor]
"""
@@ -28,6 +31,7 @@ _TRACKER_CATS = {"marketing", "statistics", "social_media", "targeting"}
# den Controls gepflegt ist). Kette: Regulation → Article → Control → Finding.
_CONTROL_MAP = {
"vague_duration": {"control_id": "AUTH-2051-A03", "regulation": "DSGVO", "article": "Art. 5 Abs. 1 lit. e + Art. 13"},
"missing_retention": {"control_id": "AUTH-2051-A03", "regulation": "DSGVO", "article": "Art. 5 Abs. 1 lit. e + Art. 13 Abs. 2 lit. a"},
"excessive_lifetime": {"control_id": "AUTH-2051-A02", "regulation": "DSGVO", "article": "Art. 5 Abs. 1 lit. e"},
"tracker_as_necessary": {"control_id": "DATA-2851-A05", "regulation": "TDDDG", "article": "§ 25 Abs. 1"},
"missing_purpose": {"control_id": "AUTH-2053-A05", "regulation": "DSGVO", "article": "Art. 13"},
@@ -232,6 +236,27 @@ def analyze_cookies(vendors: list[dict], big_lib: dict | None = None) -> dict:
),
})
# Vendor-Ebene: Verarbeitung deklariert, aber KEINE Speicherdauer. Greift,
# wenn keine Cookies gelistet UND keine persistence (z.B. Nayoki GmbH:
# 'necessary' Auftragsverarbeiter ohne Löschfrist) — Art. 5(1)(e)+13(2)(a).
v_persist = (v.get("persistence") or "").strip()
v_purpose = (v.get("purpose") or "").strip()
if not (v.get("cookies") or []) and not v_persist and (v_purpose or vcat):
findings.append({
"vendor": vname, "cookie": "(keine Cookies gelistet)",
"type": "missing_retention", "severity": "MEDIUM",
"declared": f"{vcat_label} / keine Speicherdauer",
"library_purpose": v_purpose,
"remediation": (
f"Für '{vname}' ist eine Datenverarbeitung deklariert "
f"(Kategorie '{vcat_label}'), aber keine Speicherdauer/Löschfrist "
f"angegeben und keine Cookies gelistet. Art. 5 Abs. 1 lit. e + "
f"Art. 13 Abs. 2 lit. a DSGVO verlangen eine konkrete "
f"Speicherdauer bzw. Löschfrist — bitte für '{vname}' eine "
f"Löschfrist festlegen und in der Cookie-Richtlinie ausweisen."
),
})
# A: jeden Befund an seinen Control + Rechtsgrundlage haengen (auditfest).
for f in findings:
f["control"] = _CONTROL_MAP.get(f["type"], {})