""" Action-Recipes — pro Finding-Typ eine umsetzbare Handlungsanweisung: WAS tun, WARUM (Rechtsgrundlage), WIE formulieren (konkreter Textbaustein), WO einfuegen (Doc-Abschnitt-Hinweis). Ohne Recipes ist ein Finding nur eine Diagnose. Mit Recipe weiss der Kunde sofort welchen Satz er an welche Stelle setzen muss. Verwendung: from compliance.services.finding_action_recipes import recipe_for rec = recipe_for("no_cookies_listed") # → dict mit what/why/fix_text/where/example """ from __future__ import annotations from typing import TypedDict class ActionRecipe(TypedDict, total=False): what: str # 1-Satz Diagnose why: str # Rechtsgrundlage / Risiko fix_text: str # konkreter Textbaustein zum Einfuegen where: str # in welchem Doc-Abschnitt example: str # echtes Anwendungsbeispiel severity: str # 'critical' | 'high' | 'medium' | 'low' # ─── Vendor-/Cookie-Findings (im VVT-Block) ──────────────────────── VENDOR_FINDINGS: dict[str, ActionRecipe] = { "no_cookies_listed": { "what": "Anbieter ist erfasst, aber es sind keine konkreten Cookies " "dokumentiert.", "why": "Die DSK-Orientierungshilfe Telemedien 2024 fordert pro Anbieter " "eine Auflistung aller gesetzten Cookies mit Name + Zweck + " "Speicherdauer. 'Verwendet Cookies' ohne Details erfuellt " "Art. 13 Abs. 1 lit. e DSGVO nicht.", "fix_text": "Ergaenzen Sie pro Cookie eine Zeile mit folgenden Feldern:\n" " • Cookie-Name (z.B. _ga, _fbp, NID)\n" " • Setzender Anbieter (Firma + Sitzland)\n" " • Zweck (1 Satz, z.B. 'Reichweitenmessung pseudonym')\n" " • Speicherdauer (z.B. '2 Jahre', '24h', 'Session')", "where": "Cookie-Richtlinie unter dem Abschnitt 'Kategorien' " "(Notwendig / Marketing / Statistik / ...).", "example": "_ga (Google Ireland Ltd.) — Statistik — eindeutige " "Besucher-ID — Speicherdauer 2 Jahre", "severity": "high", }, "no_country": { "what": "Anbieter-Sitzland ist nicht dokumentiert.", "why": "Art. 30 Abs. 1 lit. d DSGVO fordert die Kategorien der Empfaenger " "inkl. Sitz. Bei Drittland-Empfaengern (US, IN, CN ...) ist das " "zusaetzlich Pflicht nach Art. 13 Abs. 1 lit. f DSGVO.", "fix_text": "Fuegen Sie hinter dem Anbieternamen das Sitzland in " "Klammern ein. Bei Drittland (ausserhalb EU/EWR) zusaetzlich " "den Transfer-Mechanismus (SCC, DPF, Angemessenheitsbeschluss).", "where": "VVT-Eintrag bei 'Empfaenger' oder im Cookie-Anbieter-Listing.", "example": "Google Ireland Ltd., Dublin, Irland (EU). Bei US-Transfer: " "'Google LLC, Mountain View, US — DPF-zertifiziert'.", "severity": "high", }, "no_privacy_url": { "what": "Kein direkter Link zur Datenschutzerklaerung des Anbieters.", "why": "Art. 13 Abs. 2 lit. f DSGVO Transparenzgebot: der Nutzer muss " "die Datenverarbeitung beim Drittanbieter eigenverantwortlich " "nachvollziehen koennen.", "fix_text": "Ergaenzen Sie einen klickbaren Link zur Datenschutzerklaerung " "des Anbieters direkt neben dem Anbieternamen.", "where": "Cookie-Richtlinie pro Vendor-Eintrag. Idealerweise als " "letzter Spalteneintrag oder Inline-Link.", "example": "Google Analytics — Datenschutz: https://policies.google.com/privacy", "severity": "medium", }, "broken_privacy_url": { "what": "Der angegebene Privacy-Link gibt einen Fehler zurueck " "(404 / 403 / Timeout).", "why": "Ein toter Link erfuellt Art. 13(2)(f) DSGVO nicht — die " "Transparenz-Pflicht laeuft ins Leere.", "fix_text": "1. Pruefen Sie den Link aus einem normalen Browser. " "Funktioniert er dort? → Anbieter blockt automatisierte Pruefer (kein Mangel).\n" "2. Funktioniert er nicht? → Aktualisieren Sie den Link in Ihrer " "Cookie-Richtlinie auf die heute gueltige Privacy-URL des Anbieters.", "where": "Cookie-Richtlinie / Drittanbieter-Liste.", "example": "Wenn Adobe-Privacy-Link 404 gibt, ersetzen durch " "https://www.adobe.com/privacy/policy.html", "severity": "high", }, "no_opt_out_url": { "what": "Kein Opt-Out-Link fuer diesen Anbieter dokumentiert.", "why": "Art. 7 Abs. 3 DSGVO: der Widerruf der Einwilligung muss so " "einfach wie die Erteilung sein. Pro Drittanbieter muss eine " "Opt-Out-Moeglichkeit angeboten werden.", "fix_text": "Recherchieren Sie den offiziellen Opt-Out-Link des " "Anbieters und ergaenzen Sie ihn. Falls Ihr Cookie-Banner " "ein 'Einstellungen aendern' anbietet, ist das oft " "ausreichend — der Link sollte trotzdem als Backup " "dokumentiert sein.", "where": "Cookie-Richtlinie pro Vendor-Eintrag.", "example": "Google Analytics Opt-Out: https://tools.google.com/dlpage/gaoptout", "severity": "high", }, "broken_opt_out": { "what": "Der angegebene Opt-Out-Link funktioniert nicht " "(404 / 403 / Timeout).", "why": "Art. 7(3) DSGVO Widerrufsmoeglichkeit ohne funktionierenden " "Link ist nicht gegeben.", "fix_text": "1. Pruefen Sie ob der Link aus einem Browser funktioniert. " "403 kommt oft von Bot-Schutz und ist kein realer Mangel.\n" "2. Bei echtem 404: aktualisieren Sie auf den heute gueltigen " "Opt-Out-Link.\n" "3. Alternativ verlinken Sie auf Ihren eigenen Cookie-Banner " "'Einstellungen aendern'-Trigger.", "where": "Cookie-Richtlinie pro Vendor-Eintrag.", "example": "Wenn Criteo-Opt-Out 403 gibt, ist das oft Cloudflare-Schutz — " "Link aus dem Browser klickbar → kein Mangel. Alternativ: " "https://www.youronlinechoices.com/de/", "severity": "medium", }, } # ─── Doc-Pruefungs-Findings (im DSE/Impressum/Cookie-Pruefblock) ─── DOC_CHECK_FINDINGS: dict[str, ActionRecipe] = { "Auftragsverarbeiter erwaehnt": { "what": "Auftragsverarbeiter (Hosting, CRM, Newsletter) sind nicht " "explizit erwaehnt + es fehlt Hinweis auf AVVs nach Art. 28 DSGVO.", "why": "Art. 28 DSGVO Auftragsverarbeitung erfordert dokumentierte AVVs. " "Art. 13(1)(e) DSGVO erfordert die Nennung der Empfaenger-" "Kategorien. Fehlt der Hinweis = klassischer Pruefpunkt der " "Aufsichtsbehoerden.", "fix_text": "Wir setzen sorgfaeltig ausgewaehlte Auftragsverarbeiter " "(z.B. fuer Hosting, Webanalyse, Customer Service) ein. Mit " "allen Auftragsverarbeitern haben wir Vertraege zur " "Auftragsverarbeitung nach Art. 28 DSGVO geschlossen. Die " "Auftragsverarbeiter handeln ausschliesslich auf unsere " "Weisung und sind vertraglich zu angemessenen technischen " "und organisatorischen Massnahmen verpflichtet.", "where": "Datenschutzerklaerung im Abschnitt 'Empfaenger' oder " "'Datenuebermittlung'. Direkt nach der Aufzaehlung der " "Empfaenger-Kategorien.", "example": "Empfaenger: BMW Niederlassungen, Vertragshaendler, " "Auftragsverarbeiter (Cloud-Hosting AWS, CRM Salesforce, " "Webanalyse Adobe Analytics — mit allen sind AVVs nach " "Art. 28 DSGVO geschlossen).", "severity": "high", }, "Automatisierte Entscheidungen / Profiling": { "what": "Keine Aussage zu automatisierten Einzelentscheidungen " "oder Profiling nach Art. 22 DSGVO.", "why": "Art. 13 Abs. 2 lit. f DSGVO: bei automatisierten " "Einzelentscheidungen muessen Logik, Tragweite und Auswirkungen " "erklaert werden. Bei KEINEM Profiling muss das explizit " "verneint werden — sonst lassen Aufsichtsbehoerden Unsicherheit " "offen.", "fix_text": "Variante A (kein Profiling):\n" " 'Es findet keine automatisierte Entscheidungsfindung " "im Sinne des Art. 22 DSGVO statt. Sofern wir Profiling " "zur Anzeige personalisierter Inhalte einsetzen, erfolgt " "dies ausschliesslich auf Basis Ihrer Einwilligung und " "wird im Abschnitt [X] erlaeutert.'\n\n" "Variante B (Profiling vorhanden, z.B. fuer Werbung):\n" " 'Wir nutzen Profiling zur Anzeige personalisierter " "Werbung. Die Logik basiert auf [Klick-Historie / " "Besuchsverhalten / Praeferenzen]. Tragweite: " "Anpassung der angezeigten Anzeigen. Auswirkung: keine " "rechtlichen oder erheblichen Auswirkungen — Sie koennen " "jederzeit widersprechen unter [Link/Kontakt].'", "where": "Datenschutzerklaerung am Ende des Abschnitts " "'Betroffenenrechte' oder als eigener Absatz unter " "'Automatisierte Entscheidungen'.", "example": "Standardformulierung Variante A — falls Sie KEIN Profiling " "betreiben, ist das der sichere Default-Text.", "severity": "high", }, "Konkrete Aufsichtsbehoerde benannt": { "what": "Aufsichtsbehoerde wird nicht namentlich genannt.", "why": "Art. 13(2)(d) DSGVO: das Beschwerderecht muss konkret " "kommuniziert werden — nicht 'die zustaendige Behoerde', sondern " "Name + Anschrift + Website.", "fix_text": "Sie haben das Recht, sich bei einer Datenschutz-" "Aufsichtsbehoerde zu beschweren. Zustaendig ist:\n\n" " [Aufsichtsbehoerde mit Namen + Strasse + PLZ + Ort + Web]\n\n" "Beispiel: 'Bayerisches Landesamt fuer Datenschutzaufsicht " "(BayLDA), Promenade 18, 91522 Ansbach, www.lda.bayern.de'", "where": "Datenschutzerklaerung im Abschnitt 'Betroffenenrechte' oder " "'Beschwerderecht'.", "example": "Fuer BMW AG (Sitz Muenchen, Bayern): BayLDA, Promenade 18, " "91522 Ansbach, www.lda.bayern.de", "severity": "high", }, "Angemessenheitsbeschluss der Kommission": { "what": "Drittlandtransfer erwaehnt, aber kein Hinweis auf den " "konkreten Angemessenheitsbeschluss / DPF / SCC.", "why": "Art. 13(1)(f) DSGVO: bei Drittlandtransfer muss der " "Transfer-Mechanismus benannt sein (Angemessenheitsbeschluss, " "Standardvertragsklauseln, BCR, Ausnahmen nach Art. 49).", "fix_text": "Fuer Datenuebermittlungen in die USA stuetzen wir uns auf " "den Angemessenheitsbeschluss der EU-Kommission vom " "10.07.2023 (EU-US Data Privacy Framework / DPF). Sofern " "der Empfaenger DPF-zertifiziert ist, ist die Uebermittlung " "rechtlich abgesichert. Bei nicht-zertifizierten Empfaengern " "ergaenzen wir EU-Standardvertragsklauseln (SCC) gemaess " "Durchfuehrungsbeschluss 2021/914.", "where": "Datenschutzerklaerung im Abschnitt 'Drittlandtransfer' oder " "'Internationale Datenuebermittlung'.", "example": "Konkret: Google LLC (USA) ist DPF-zertifiziert " "(Zertifikat einsehbar unter dataprivacyframework.gov).", "severity": "high", }, "Anschrift des Verantwortlichen": { "what": "In der Cookie-Richtlinie fehlt die vollstaendige Anschrift.", "why": "Art. 13(1)(a) DSGVO: der Verantwortliche muss eindeutig " "identifizierbar sein. Cookie-Richtlinie + DSE muessen " "konsistente Angaben enthalten.", "fix_text": "Verantwortlich fuer die Datenverarbeitung im Sinne der " "DSGVO ist:\n [Firmenname]\n [Strasse + Hausnummer]\n " "[PLZ + Ort]\n [Land]\n E-Mail: [...]", "where": "Cookie-Richtlinie am Anfang ODER Verweis-Link zur DSE.", "example": "Bayerische Motoren Werke Aktiengesellschaft, Petuelring 130, " "80809 Muenchen, Deutschland", "severity": "high", }, "Konkrete Cookie-Namen aufgelistet": { "what": "Cookies werden allgemein erwaehnt aber ohne konkrete Namen + " "Speicherdauer.", "why": "DSK-Orientierungshilfe Telemedien: Auflistung jedes einzelnen " "Cookies mit Name. Generische Aussagen ('wir nutzen " "Werbe-Cookies') sind unzureichend.", "fix_text": "Erweitern Sie die Cookie-Tabelle um die Spalten:\n" " Name | Anbieter | Zweck | Speicherdauer\n\n" "Browser-Devtools (Application > Cookies) zeigt die " "tatsaechlich gesetzten Namen — bitte Cookie-Liste " "regelmaessig synchronisieren.", "where": "Cookie-Richtlinie im Abschnitt 'Verwendete Cookies'.", "example": "_ga | Google Ireland | Statistik (Besucher-ID) | 2 Jahre\n" "_fbp | Meta Platforms IE | Werbung (Browser-ID) | 90 Tage", "severity": "high", }, "Konkrete Speicherdauern pro Cookie": { "what": "Speicherdauer nur pauschal oder als generischer Bereich.", "why": "Art. 13(2)(a) DSGVO: konkrete Speicherdauer oder Kriterien " "fuer die Festlegung. DSK fordert Speicherdauer PRO Cookie.", "fix_text": "Pro Cookie eine konkrete Zeitangabe in der Cookie-Tabelle " "ergaenzen: 'Session', '24 Stunden', '90 Tage', '2 Jahre'.", "where": "Cookie-Richtlinie in der Cookie-Tabelle.", "example": "JSESSIONID: Session — _ga: 2 Jahre — _fbp: 90 Tage", "severity": "high", }, "Opt-Out-Links pro Drittanbieter": { "what": "Pro Drittanbieter ist kein direkter Opt-Out-Link angegeben.", "why": "Art. 7(3) DSGVO Widerrufsmoeglichkeit. EuGH Planet49 " "(C-673/17) bestaetigt: Widerruf muss so einfach wie Einwilligung sein.", "fix_text": "Pro Drittanbieter eine Spalte 'Opt-Out' ergaenzen mit " "direktem Link. Alternativ: zentralen 'Cookie-" "Einstellungen aendern'-Button im Footer der Webseite + " "Hinweis darauf in der Cookie-Richtlinie.", "where": "Cookie-Richtlinie pro Vendor-Eintrag oder als zentraler " "Abschnitt 'Wie kann ich widersprechen?'.", "example": "Google Analytics: https://tools.google.com/dlpage/gaoptout\n" "Meta Pixel: ueber Facebook-Konto-Einstellungen", "severity": "high", }, "Privacy-Policy-Links pro Drittanbieter": { "what": "Pro Drittanbieter ist kein direkter Datenschutz-Link gesetzt.", "why": "Art. 13(2)(f) DSGVO Transparenzgebot — Nutzer muss die " "Datenverarbeitung beim Drittanbieter eigenverantwortlich " "nachvollziehen koennen.", "fix_text": "Pro Drittanbieter Link auf dessen Datenschutzerklaerung " "ergaenzen. Tabelle 'Anbieter | Datenschutz-Link'.", "where": "Cookie-Richtlinie im Drittanbieter-Listing.", "example": "Adobe Analytics: https://www.adobe.com/privacy/policy.html", "severity": "medium", }, "Rechtswidriger Haftungsausschluss fuer Links": { "what": "Klassischer Link-Disclaimer ('Wir distanzieren uns von verlinkten " "Inhalten') ist im Impressum.", "why": "BGH I ZR 317/01: solche Disclaimer sind rechtlich wirkungslos. " "Sie befreien NICHT von der Stoererhaftung und koennen sogar " "den gegenteiligen Effekt haben (Anerkennung der eigenen " "Pruefpflicht).", "fix_text": "Entfernen Sie pauschale Link-Disclaimer vollstaendig aus " "dem Impressum. Bei Bedarf ein knapper, zutreffender Satz:\n" " 'Fuer den Inhalt verlinkter externer Webseiten ist " "ausschliesslich deren Betreiber verantwortlich.'", "where": "Impressum am Ende des Dokuments.", "example": "Statt 'Wir distanzieren uns ausdruecklich von allen " "Inhalten verlinkter Seiten' — einfach nichts schreiben.", "severity": "low", }, "Verbraucherstreitbeilegung / OS-Plattform": { "what": "Kein Link zur EU-OS-Plattform bzw. keine Aussage zur " "Streitbeilegung.", "why": "Art. 14 EU-VO 524/2013 (B2C-Anbieter, Online-Verkauf): " "klickbarer Link auf https://ec.europa.eu/consumers/odr " "PFLICHT. §36 VSBG: Aussage ob teilnehmend oder nicht.", "fix_text": "Die EU-Kommission stellt eine Plattform zur Online-" "Streitbeilegung (OS) bereit, die Sie unter " "" "https://ec.europa.eu/consumers/odr erreichen.\n\n" "Wir sind nicht bereit oder verpflichtet, an " "Streitbeilegungsverfahren vor einer " "Verbraucherschlichtungsstelle teilzunehmen.", "where": "Impressum am Ende, eigener Abschnitt 'Streitbeilegung'.", "example": "Standardformulierung anwendbar fuer alle B2C-Anbieter ohne " "ODR-Teilnahme.", "severity": "high", }, "Name der vertretungsberechtigten Person": { "what": "Vertretungsberechtigte Person ist nicht namentlich mit " "Funktionsbezeichnung genannt.", "why": "§5 Abs. 1 Nr. 1 TMG: bei juristischen Personen sind die " "Vertretungsberechtigten namentlich zu nennen.", "fix_text": "Ergaenzen Sie nach der Firmenbezeichnung:\n" " 'Vertreten durch: [Vorstand / Geschaeftsfuehrung] " "[Vorname Nachname]'", "where": "Impressum direkt nach Firmenname + Anschrift.", "example": "Vertreten durch: Vorstandsvorsitzender Oliver Zipse", "severity": "high", }, } def recipe_for(finding_key: str) -> ActionRecipe | None: """Lookup Recipe by finding-key. Vendors + Doc-Findings in einem Lookup.""" if finding_key in VENDOR_FINDINGS: return VENDOR_FINDINGS[finding_key] if finding_key in DOC_CHECK_FINDINGS: return DOC_CHECK_FINDINGS[finding_key] # Fuzzy match auf Doc-Findings (label kann variieren) fk = finding_key.lower() for k, v in DOC_CHECK_FINDINGS.items(): if k.lower() in fk or fk in k.lower(): return v return None