Merge feat/zeroclaw-compliance-agent into main
Brings all compliance doc-check features: - 162 regex checks + 1874 Master Controls - LLM-agnostic agent with tool calling - Banner check (46 checks, 30 CMPs, stealth, Shadow DOM) - Impressum check (24 checks) - Deep consent verification (DataLayer, GCM, TCF) - CMP E2E tests (39 tests) - HTML email reports, FAQ, persistent history Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
# Instruktion: Hartkodiertes Wissen → Control Library Migration
|
||||
|
||||
**Branch:** `feat/zeroclaw-compliance-agent`
|
||||
**Repo:** `/Users/benjaminadmin/Projekte/breakpilot-compliance/`
|
||||
**Erstellt:** 2026-04-29
|
||||
**Review-Deadline:** 2026-07-01
|
||||
|
||||
## Problem
|
||||
|
||||
Der Compliance Agent hat 6 Dateien mit hartkodiertem Rechtswissen in Python-Dicts.
|
||||
Das Wissen veraltet und wird nicht von der Pipeline aktualisiert. Langfristig muss
|
||||
alles aus der Control Library kommen (166k+ Controls in `compliance.canonical_controls`).
|
||||
|
||||
## Inventar (alle im Backend: `backend-compliance/compliance/services/`)
|
||||
|
||||
| Datei | Hartkodiert | Zeilen | Prioritaet |
|
||||
|-------|------------|--------|-----------|
|
||||
| `legal_basis_validator.py` | `CORRECT_BASIS` dict — 7 Lit-Zuordnungen (Art. 6 lit. a-f pro Zweck) | ~50 LOC | HOCH |
|
||||
| `service_registry.py` | `SERVICE_REGISTRY` dict — 82 Services mit Legal Refs | ~500 LOC | MITTEL |
|
||||
| `mandatory_content_checker.py` | `MANDATORY_DSE_CONTENT` (9 Felder) + `MANDATORY_IMPRESSUM_CONTENT` (5 Felder) | ~80 LOC | MITTEL |
|
||||
| `relevance_filter.py` | `CONTROL_RELEVANCE` dict — 7 Controls mit Keyword-Listen | ~60 LOC | MITTEL |
|
||||
| `consent-tester/services/script_analyzer.py` | `SERVICE_PATTERNS` — 19 Services (Duplikat von Registry) | ~70 LOC | NIEDRIG |
|
||||
| `consent-tester/services/banner_detector.py` | `CMP_SELECTORS` — 10 CMPs | PERMANENT (technisch) | — |
|
||||
|
||||
## Migrationspfad pro Datei
|
||||
|
||||
### Schritt 1: Controls in der Pipeline generieren
|
||||
|
||||
Fuer jedes Dict-Entry einen entsprechenden Control in der Pipeline generieren lassen.
|
||||
|
||||
Beispiel fuer `legal_basis_validator.py`:
|
||||
|
||||
```sql
|
||||
-- Neuer Control in canonical_controls:
|
||||
INSERT INTO compliance.canonical_controls (title, objective, requirements, scope_conditions, tags)
|
||||
VALUES (
|
||||
'Cookie-Tracking erfordert Einwilligung (lit. a)',
|
||||
'Cookie-Tracking und Webanalyse duerfen nur mit ausdruecklicher Einwilligung (Art. 6 Abs. 1 lit. a DSGVO) erfolgen.',
|
||||
'Rechtsgrundlage fuer Cookie-Tracking muss Art. 6(1)(a) sein. Art. 6(1)(f) (berechtigtes Interesse) ist nach EuGH C-673/17 (Planet49) nicht zulaessig.',
|
||||
'{"applies_when": "cookie_tracking OR web_analytics"}',
|
||||
ARRAY['legal_basis', 'lit_mapping', 'cookie', 'planet49']
|
||||
);
|
||||
```
|
||||
|
||||
Benoetigte Controls (aus legal_basis_validator.py CORRECT_BASIS):
|
||||
1. `cookie_tracking` → lit. a (Planet49)
|
||||
2. `web_analytics` → lit. a (DSK Orientierungshilfe, §25 TDDDG)
|
||||
3. `marketing_email` → lit. a (Art. 7 DSGVO, §7 UWG)
|
||||
4. `remarketing` → lit. a (§25 TDDDG)
|
||||
5. `credit_check` → lit. b/f + Art. 22 Pflichthinweis
|
||||
6. `social_media_embed` → lit. a (Fashion ID Urteil)
|
||||
7. `session_recording` → lit. a (§25 TDDDG)
|
||||
|
||||
Benoetigte Controls (aus mandatory_content_checker.py):
|
||||
1. DSE muss Verantwortlichen nennen (Art. 13(1)(a))
|
||||
2. DSE muss DSB-Kontakt nennen (Art. 13(1)(b))
|
||||
3. DSE muss Zwecke nennen (Art. 13(1)(c))
|
||||
4. DSE muss Rechtsgrundlagen nennen (Art. 13(1)(c))
|
||||
5. DSE muss Speicherdauer nennen (Art. 13(2)(a))
|
||||
6. DSE muss Betroffenenrechte nennen (Art. 13(2)(b-d))
|
||||
7. DSE muss Beschwerderecht nennen (Art. 13(2)(d))
|
||||
8. DSE muss Drittlandtransfer nennen (Art. 13(1)(f))
|
||||
9. DSE muss automatisierte Entscheidungen nennen (Art. 13(2)(f))
|
||||
|
||||
### Schritt 2: Agent-Code aendern — Control Library first, Dict as Fallback
|
||||
|
||||
```python
|
||||
# VORHER (hartkodiert):
|
||||
CORRECT_BASIS = {"cookie_tracking": {"correct": "lit. a", ...}}
|
||||
|
||||
# NACHHER (Control Library first):
|
||||
async def get_legal_basis_rule(purpose: str) -> dict | None:
|
||||
controls = await query_controls(tags=["lit_mapping", purpose])
|
||||
if controls:
|
||||
return controls[0] # Control Library hat Vorrang
|
||||
logger.warning("No control found for %s — using hardcoded fallback", purpose)
|
||||
return CORRECT_BASIS.get(purpose) # Fallback
|
||||
```
|
||||
|
||||
### Schritt 3: Dicts entfernen
|
||||
|
||||
Wenn alle Controls in der Library sind und der Agent sie zuverlaessig findet:
|
||||
- Dicts loeschen
|
||||
- Warning-Logs entfernen
|
||||
- Tests aktualisieren
|
||||
|
||||
## Welche Datei NICHT migriert wird
|
||||
|
||||
`banner_detector.py` — die CMP-Selektoren sind technische CSS-Patterns, kein
|
||||
Rechtswissen. Die bleiben hartkodiert und werden aktualisiert wenn CMPs ihre UI aendern.
|
||||
|
||||
## Erkennungszeichen im Code
|
||||
|
||||
Alle betroffenen Dateien haben:
|
||||
- `⚠️ TECHNISCHE SCHULD` im Docstring
|
||||
- `Review-Datum: 2026-07-01` im Header
|
||||
- `logger.warning("... HARDCODED rules ...")` bei Nutzung
|
||||
|
||||
## Zusaetzliches Problem: Keyword-basierte Pflichtinhalte-Pruefung
|
||||
|
||||
### Problem (identifiziert beim IHK Konstanz Test, 2026-04-29)
|
||||
|
||||
Der `mandatory_content_checker.py` prueft ob DSE-Pflichtangaben vorhanden sind
|
||||
per Keyword-Matching: `"zweck" in text.lower()`. Das bricht wenn:
|
||||
|
||||
- Andere Formulierung: "Verarbeitungszwecke" statt "Zwecke"
|
||||
- Andere Sprache: Englische DSE auf deutscher Website
|
||||
- Umschreibung: "Wozu wir Ihre Daten nutzen" statt "Zwecke"
|
||||
|
||||
Gleiches Problem bei `ECOMMERCE_INDICATORS` in der gleichen Datei — hartkodierte
|
||||
Shop-Erkennung die neue Shop-Systeme nicht kennt.
|
||||
|
||||
### Betroffene Stellen
|
||||
|
||||
| Datei | Dict/Liste | Zeilen |
|
||||
|-------|-----------|--------|
|
||||
| `mandatory_content_checker.py` | `MANDATORY_DSE_CONTENT` keywords (9 Felder) | ~60 LOC |
|
||||
| `mandatory_content_checker.py` | `MANDATORY_IMPRESSUM_CONTENT` keywords (5 Felder) | ~30 LOC |
|
||||
| `mandatory_content_checker.py` | `ECOMMERCE_INDICATORS` | ~10 LOC |
|
||||
|
||||
### Loesung: LLM-basierte Pflichtinhalte-Pruefung
|
||||
|
||||
Statt hartkodierter Keywords → Qwen fragen:
|
||||
|
||||
```python
|
||||
# VORHER (hartkodiert, bricht bei neuer Formulierung):
|
||||
found = any(kw in text_lower for kw in ["zweck", "purpose", "verarbeitungszweck"])
|
||||
|
||||
# NACHHER (LLM-basiert, sprachunabhaengig):
|
||||
prompt = f"""
|
||||
Pruefe ob der folgende Text Angaben zu den Zwecken der Datenverarbeitung
|
||||
enthaelt (Art. 13 Abs. 1 lit. c DSGVO).
|
||||
Antworte NUR mit: JA (mit kurzem Zitat) oder NEIN.
|
||||
Text: {dse_text[:2000]}
|
||||
"""
|
||||
result = await query_qwen(prompt)
|
||||
found = result.startswith("JA")
|
||||
```
|
||||
|
||||
### Aufwand
|
||||
|
||||
- 9 Pflichtfelder × 1 LLM-Call = 9 Calls (parallelisierbar)
|
||||
- ~100 LOC Umbau
|
||||
- Fallback auf Keywords wenn LLM nicht verfuegbar
|
||||
|
||||
### Prioritaet
|
||||
|
||||
MITTEL — die erweiterten Keywords funktionieren fuer 90% der Faelle.
|
||||
LLM-Pruefung ist robuster aber langsamer (~30s statt <1s).
|
||||
|
||||
## Memory-Datei
|
||||
|
||||
Details: `/Users/benjaminadmin/.claude/projects/-Users-benjaminadmin-Projekte-breakpilot-lehrer/memory/hardcoded_knowledge_debt.md`
|
||||
@@ -0,0 +1,560 @@
|
||||
# Plan: Compliance Agent — Vom PoC zum Produkt
|
||||
|
||||
## Kontext
|
||||
|
||||
Der Compliance Agent PoC funktioniert: URL scannen, Qwen klassifiziert,
|
||||
UCCA bewertet, Dienstleister erkannt, Korrekturvorschlaege generiert, Email gesendet.
|
||||
|
||||
Aber: Scores sind zu niedrig, zu viele False-Positive Controls, kein Consent-Test,
|
||||
keine Persistenz, keine PDF-Exports. Dieser Plan macht das PoC produktreif.
|
||||
|
||||
**Strategische Bedeutung:** Erstmalig wird das RAG (166k Controls) gegen echte
|
||||
Webseiten getestet. Der Consent-Test (vor/nach Cookie-Einwilligung) waere ein
|
||||
Alleinstellungsmerkmal das kein Wettbewerber hat.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: UCCA Score-Kalibrierung (P0, 2-3 Tage)
|
||||
|
||||
### Problem
|
||||
|
||||
Der UCCA-Score fuer Opodo war LOW (20/100). Realistisch waere MEDIUM (40-50).
|
||||
Die Intake-Flags werden aus dem Text extrahiert, aber zu wenige werden gesetzt.
|
||||
|
||||
### Loesung
|
||||
|
||||
**0.1 Intelligentere Intake-Flag-Erkennung**
|
||||
|
||||
Aktuell: einfache Keyword-Suche (`"werbung" in text.lower()`).
|
||||
Neu: LLM-gestuetzte Extraktion der Intake-Flags.
|
||||
|
||||
```python
|
||||
# Statt:
|
||||
"marketing": "werbung" in text.lower()
|
||||
|
||||
# Neu: Qwen extrahiert strukturiert
|
||||
prompt = """
|
||||
Analysiere diesen Text und setze folgende Flags auf true/false:
|
||||
- personal_data: Werden personenbezogene Daten verarbeitet?
|
||||
- customer_data: Werden Kundendaten gespeichert?
|
||||
- marketing: Werden Daten fuer Werbung/Marketing genutzt?
|
||||
- profiling: Findet Profiling oder Personalisierung statt?
|
||||
- minor_data: Werden Daten von Minderjaehrigen verarbeitet?
|
||||
- biometric_data: Werden biometrische Daten verarbeitet?
|
||||
- location_data: Werden Standortdaten erhoben?
|
||||
- third_party_sharing: Werden Daten an Dritte weitergegeben?
|
||||
- automated_decisions: Werden automatisierte Entscheidungen getroffen?
|
||||
- cross_border_transfer: Findet Drittlandtransfer statt?
|
||||
Antworte als JSON.
|
||||
"""
|
||||
```
|
||||
|
||||
**0.2 Score-Gewichtung anpassen**
|
||||
|
||||
Die UCCA-Engine (Go, `ai-compliance-sdk/internal/ucca/`) hat die Score-Berechnung.
|
||||
Pruefen ob die Gewichte realistisch sind:
|
||||
- Personenbezogene Daten allein = 10 Punkte (zu wenig)
|
||||
- Marketing + Drittlandtransfer + Profiling sollte mindestens +30 geben
|
||||
- Zahlungsdaten + Passdaten sollte +20 geben
|
||||
|
||||
**Dateien:**
|
||||
- `backend-compliance/compliance/api/agent_analyze_routes.py` — Flag-Extraktion
|
||||
- `ai-compliance-sdk/internal/ucca/engine.go` — Score-Berechnung (Go)
|
||||
- `ai-compliance-sdk/internal/ucca/rules.go` — Regel-Definitionen
|
||||
|
||||
**Testfaelle:**
|
||||
- Opodo: Soll MEDIUM (40-50) ergeben
|
||||
- Google: Soll HIGH (60-70) ergeben
|
||||
- Einfacher Blog ohne Tracking: Soll MINIMAL (0-10) ergeben
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Control Relevance Filter (P0, 1 Tag)
|
||||
|
||||
### Bereits geplant in PLAN-control-relevance-filter.md
|
||||
|
||||
Nur Phase 1 (regelbasiert) hier umsetzen:
|
||||
|
||||
1. Neues `relevance_conditions` JSONB-Feld auf `canonical_controls`
|
||||
2. Top-20 generische Controls mit Keywords versehen
|
||||
3. Filter-Funktion im Agent: Control nur empfehlen wenn Keywords im Text vorkommen
|
||||
4. Test: C_TRANSPARENCY faellt bei Opodo weg (kein KI-Nachweis)
|
||||
|
||||
**Datei:** `backend-compliance/compliance/services/relevance_filter.py` (~200 LOC)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Headless Browser Consent-Test (P1, 2-3 Tage)
|
||||
|
||||
### Das Killer-Feature
|
||||
|
||||
Automatischer 3-Phasen-Test:
|
||||
|
||||
```
|
||||
Phase A: Erster Besuch (ohne Interaktion)
|
||||
→ Welche Scripts/Cookies laden VOR dem Consent-Banner?
|
||||
→ Finding: "Script X laedt ohne Einwilligung"
|
||||
|
||||
Phase B: Consent ablehnen ("Nur notwendige")
|
||||
→ Button klicken, 3 Sek warten
|
||||
→ Welche Scripts/Cookies laden NACH Ablehnung?
|
||||
→ Finding: "Google Analytics laedt trotz Ablehnung" = schwerer Verstoss
|
||||
|
||||
Phase C: Consent akzeptieren ("Alle akzeptieren")
|
||||
→ Neuer Browser-Kontext, akzeptieren klicken
|
||||
→ Welche Scripts/Cookies laden NACH Zustimmung?
|
||||
→ Abgleich mit Cookie-Policy: "TikTok Pixel laedt, ist aber nicht dokumentiert"
|
||||
```
|
||||
|
||||
### Technische Implementierung
|
||||
|
||||
**2.1 Playwright im Backend-Container**
|
||||
|
||||
```dockerfile
|
||||
# Ergaenzung im backend-compliance Dockerfile
|
||||
RUN pip install playwright && playwright install chromium
|
||||
```
|
||||
|
||||
Alternativ: eigener `consent-tester` Service (besser isoliert, ~200MB Image).
|
||||
|
||||
**2.2 Consent Test Service**
|
||||
|
||||
```python
|
||||
# backend-compliance/compliance/services/consent_tester.py (~250 LOC)
|
||||
|
||||
class ConsentTester:
|
||||
async def test(self, url: str) -> ConsentTestResult:
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
|
||||
# Phase A: Ohne Consent
|
||||
pre = await self._scan_phase(browser, url, action=None)
|
||||
|
||||
# Phase B: Ablehnen
|
||||
reject = await self._scan_phase(browser, url, action="reject")
|
||||
|
||||
# Phase C: Akzeptieren
|
||||
accept = await self._scan_phase(browser, url, action="accept")
|
||||
|
||||
await browser.close()
|
||||
|
||||
return ConsentTestResult(
|
||||
banner_detected=pre.banner_visible,
|
||||
banner_type=pre.banner_provider, # Didomi, OneTrust, Cookiebot etc.
|
||||
scripts_before_consent=pre.scripts,
|
||||
cookies_before_consent=pre.cookies,
|
||||
violations_before_consent=self._find_violations(pre),
|
||||
scripts_after_reject=reject.scripts,
|
||||
cookies_after_reject=reject.cookies,
|
||||
violations_after_reject=self._find_violations(reject),
|
||||
scripts_after_accept=accept.scripts,
|
||||
cookies_after_accept=accept.cookies,
|
||||
undocumented_after_accept=self._find_undocumented(accept, dse_text),
|
||||
)
|
||||
```
|
||||
|
||||
**2.3 Banner-Button-Erkennung**
|
||||
|
||||
```python
|
||||
# Typische Consent-Banner Button-Patterns
|
||||
ACCEPT_PATTERNS = [
|
||||
'button:has-text("Alle akzeptieren")',
|
||||
'button:has-text("Alles akzeptieren")',
|
||||
'button:has-text("Accept all")',
|
||||
'button:has-text("Alle Cookies akzeptieren")',
|
||||
'[class*="accept-all"]',
|
||||
'[data-action="accept-all"]',
|
||||
'#didomi-notice-agree-button', # Didomi
|
||||
'.cky-btn-accept', # CookieYes
|
||||
'#onetrust-accept-btn-handler', # OneTrust
|
||||
'#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll', # Cookiebot
|
||||
]
|
||||
|
||||
REJECT_PATTERNS = [
|
||||
'button:has-text("Nur notwendige")',
|
||||
'button:has-text("Ablehnen")',
|
||||
'button:has-text("Reject")',
|
||||
'button:has-text("Nur essentielle")',
|
||||
'[class*="reject"]',
|
||||
'#didomi-notice-disagree-button',
|
||||
'#onetrust-reject-all-handler',
|
||||
'#CybotCookiebotDialogBodyButtonDecline',
|
||||
]
|
||||
```
|
||||
|
||||
**2.4 Violation-Erkennung**
|
||||
|
||||
```python
|
||||
def _find_violations(self, phase: ScanPhase) -> list[Violation]:
|
||||
violations = []
|
||||
|
||||
for script in phase.scripts:
|
||||
service = match_service(script) # Gegen SERVICE_REGISTRY
|
||||
if service and service.requires_consent:
|
||||
if phase.action is None:
|
||||
# Script laedt VOR Consent → Verstoss
|
||||
violations.append(Violation(
|
||||
severity="HIGH",
|
||||
service=service.name,
|
||||
legal_ref="§25 TDDDG",
|
||||
text=f"{service.name} laedt OHNE vorherige Einwilligung",
|
||||
))
|
||||
elif phase.action == "reject":
|
||||
# Script laedt NACH Ablehnung → schwerer Verstoss
|
||||
violations.append(Violation(
|
||||
severity="CRITICAL",
|
||||
service=service.name,
|
||||
legal_ref="§25 TDDDG, Art. 5(3) ePrivacy",
|
||||
text=f"{service.name} laedt TROTZ Ablehnung — Dark Pattern",
|
||||
))
|
||||
|
||||
return violations
|
||||
```
|
||||
|
||||
**2.5 Frontend: Consent-Test Tab**
|
||||
|
||||
Dritter Tab im Agent: "Schnellanalyse | Website-Scan | Cookie-Test"
|
||||
|
||||
Anzeige:
|
||||
```
|
||||
Cookie-Consent-Test: opodo.de
|
||||
═══════════════════════════════
|
||||
|
||||
Banner erkannt: Ja (Didomi)
|
||||
|
||||
Phase A: Vor Einwilligung
|
||||
✗ Google Analytics — laedt ohne Einwilligung (§25 TDDDG)
|
||||
✓ Didomi CMP — notwendig, OK
|
||||
✗ Google Tag Manager — laedt ohne Einwilligung
|
||||
|
||||
Phase B: Nach Ablehnung ("Nur notwendige")
|
||||
✗ Google Analytics — laedt TROTZ Ablehnung (KRITISCH!)
|
||||
✓ Keine neuen Tracking-Cookies gesetzt
|
||||
|
||||
Phase C: Nach Zustimmung ("Alle akzeptieren")
|
||||
✓ Google Analytics — jetzt aktiv (mit Consent OK)
|
||||
✓ Didomi Consent Cookie gesetzt
|
||||
✗ TikTok Pixel — nicht in Cookie-Policy dokumentiert
|
||||
|
||||
Zusammenfassung:
|
||||
2 kritische Verstoesse (Tracking ohne/trotz Ablehnung)
|
||||
1 Dokumentationsluecke (TikTok nicht in Policy)
|
||||
```
|
||||
|
||||
**2.6 Neuer Endpoint**
|
||||
|
||||
```
|
||||
POST /api/compliance/agent/consent-test
|
||||
Body: { "url": "https://www.opodo.de" }
|
||||
Response: ConsentTestResult (3 Phasen + Violations)
|
||||
```
|
||||
|
||||
**Dateien:**
|
||||
- `backend-compliance/compliance/services/consent_tester.py` — Playwright Test (~250 LOC)
|
||||
- `backend-compliance/compliance/api/agent_consent_routes.py` — Endpoint (~100 LOC)
|
||||
- `admin-compliance/app/sdk/agent/_components/ConsentTestResult.tsx` — UI (~150 LOC)
|
||||
- `admin-compliance/app/api/sdk/v1/agent/consent-test/route.ts` — Proxy (~35 LOC)
|
||||
|
||||
**Aufwand:** 2-3 Tage (inkl. Playwright Setup + Button-Erkennung)
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Dienstleister-Registry erweitern (P1, 1 Tag)
|
||||
|
||||
### Aktuell: ~20 Services in website_scanner.py
|
||||
|
||||
### Ziel: 80+ Services
|
||||
|
||||
Neue Kategorien:
|
||||
- **Newsletter/Email Marketing** — Mailchimp, Brevo, CleverReach, ActiveCampaign, HubSpot, Rapidmail
|
||||
- **Social Media Embeds** — Twitter/X, Instagram, LinkedIn, Pinterest, TikTok
|
||||
- **A/B Testing** — Optimizely, VWO, Google Optimize (Legacy)
|
||||
- **Heatmaps/Session Recording** — FullStory, Mouseflow, Crazy Egg, Lucky Orange
|
||||
- **Werbenetwerke** — Google Ads, Meta Ads, TikTok Ads, Criteo, Taboola, Outbrain
|
||||
- **Tag Manager** — Google, Tealium, Segment
|
||||
- **CRM** — HubSpot, Salesforce Pardot, Pipedrive
|
||||
- **Push Notifications** — OneSignal, Pushwoosh, Firebase
|
||||
- **Customer Support** — Freshdesk, Zendesk (erweitern), HelpScout
|
||||
- **Cloud/CDN** — AWS CloudFront, Azure CDN, Vercel, Netlify
|
||||
- **Error Tracking** — Sentry, Bugsnag, Datadog RUM, New Relic
|
||||
|
||||
Jeder Eintrag: Regex, Provider, Land, EU-Adaequanz, Consent-Pflicht, Rechtsgrundlage.
|
||||
|
||||
**Datei:** `backend-compliance/compliance/services/website_scanner.py` — SERVICE_REGISTRY erweitern
|
||||
Evtl. in eigene Datei auslagern: `service_registry.py` (~300 LOC, reine Daten)
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Scan beschleunigen (P2, 1 Tag)
|
||||
|
||||
### Problem
|
||||
|
||||
Aktuell: Seiten sequentiell fetchen + 3-4 LLM-Calls = 3-5 Minuten.
|
||||
|
||||
### Loesung
|
||||
|
||||
**4.1 Parallel Fetchen**
|
||||
```python
|
||||
# Statt sequentiell:
|
||||
for url in pages:
|
||||
html = await fetch(url)
|
||||
|
||||
# Parallel:
|
||||
htmls = await asyncio.gather(*[fetch(url) for url in pages])
|
||||
```
|
||||
|
||||
**4.2 Qwen-Calls reduzieren**
|
||||
- DSE-Extraktion: Nur wenn Datenschutzseite gefunden
|
||||
- Korrekturvorschlaege: Nur fuer HIGH-Severity Findings (nicht fuer alle)
|
||||
- Batch: Alle Korrekturen in einem LLM-Call statt einzeln
|
||||
|
||||
**4.3 Kleineres Modell fuer Klassifizierung**
|
||||
- Qwen 2.5:14b statt 3.5:35b fuer einfache Klassifizierung (~5x schneller)
|
||||
- 3.5:35b nur fuer Korrekturvorschlaege und DSE-Extraktion
|
||||
|
||||
**Ziel:** Scan in <60 Sekunden statt 3-5 Minuten.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Persistenz — Ergebnisse in DB speichern (P2, 1 Tag)
|
||||
|
||||
### Problem
|
||||
|
||||
Ergebnisse sind aktuell nur im Browser-Session-State und in Mailpit-Emails.
|
||||
Bei Seitenreload oder neuem Tab: alles weg.
|
||||
|
||||
### Loesung
|
||||
|
||||
**5.1 Neue DB-Tabelle**
|
||||
|
||||
```sql
|
||||
CREATE TABLE compliance_agent_scans (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
mode TEXT NOT NULL, -- 'quick', 'scan', 'consent_test'
|
||||
analysis_mode TEXT NOT NULL, -- 'pre_launch', 'post_launch'
|
||||
classification TEXT,
|
||||
risk_level TEXT,
|
||||
risk_score FLOAT,
|
||||
escalation_level TEXT,
|
||||
services JSONB DEFAULT '[]',
|
||||
findings JSONB DEFAULT '[]',
|
||||
corrections JSONB DEFAULT '[]',
|
||||
consent_test JSONB, -- Phase 2 Ergebnisse
|
||||
summary_html TEXT,
|
||||
email_sent BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_agent_scans_tenant ON compliance_agent_scans(tenant_id);
|
||||
CREATE INDEX idx_agent_scans_url ON compliance_agent_scans(url);
|
||||
```
|
||||
|
||||
**5.2 Frontend: Scan-Verlauf aus DB laden**
|
||||
|
||||
Statt Session-basierter History → API-Call: `GET /api/compliance/agent/scans`
|
||||
Sortiert nach Datum, filterbar nach URL/Risiko/Typ.
|
||||
|
||||
**Dateien:**
|
||||
- `backend-compliance/compliance/api/agent_scan_routes.py` — DB-Save nach jedem Scan
|
||||
- `backend-compliance/compliance/db/agent_scan_models.py` — SQLAlchemy Model (~40 LOC)
|
||||
- Migration SQL
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: PDF-Export (P2, 0.5 Tage)
|
||||
|
||||
### Nutzen
|
||||
|
||||
Manager wollen druckbare Reports, nicht nur Emails.
|
||||
|
||||
### Implementierung
|
||||
|
||||
ReportLab oder WeasyPrint (bereits im Backend vorhanden fuer andere PDF-Exports).
|
||||
|
||||
```python
|
||||
# Neuer Endpoint
|
||||
GET /api/compliance/agent/scans/{id}/pdf
|
||||
|
||||
# Generiert PDF mit:
|
||||
# - Deckblatt (Firmenlogo, Datum, URL)
|
||||
# - Executive Summary (Risiko-Ampel, Rolle)
|
||||
# - Findings-Tabelle
|
||||
# - Dienstleister-Abgleich (SOLL/IST)
|
||||
# - Consent-Test Ergebnisse (wenn vorhanden)
|
||||
# - Korrekturvorschlaege
|
||||
# - Anhang: Gescannte Seiten, Rechtsgrundlagen
|
||||
```
|
||||
|
||||
**Datei:** `backend-compliance/compliance/services/agent_pdf_export.py` (~200 LOC)
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Recurring Scans / ZeroClaw (P3, 1 Tag)
|
||||
|
||||
### Nutzen
|
||||
|
||||
Website aendert sich → neue Dienstleister eingebunden → automatisches Alert.
|
||||
|
||||
### Implementierung
|
||||
|
||||
ZeroClaw SOP mit Cron-Trigger:
|
||||
|
||||
```toml
|
||||
[[triggers]]
|
||||
type = "cron"
|
||||
schedule = "0 6 * * 1" # Jeden Montag 06:00
|
||||
```
|
||||
|
||||
Der Agent:
|
||||
1. Laedt alle gespeicherten URLs aus der DB
|
||||
2. Scannt jede URL
|
||||
3. Vergleicht mit letztem Scan-Ergebnis
|
||||
4. Bei Aenderungen: Email an DSB mit Diff
|
||||
|
||||
**Oder:** Einfacher Cron-Job im Backend (kein ZeroClaw noetig):
|
||||
```python
|
||||
# backend-compliance/compliance/services/recurring_scan.py
|
||||
async def run_weekly_scans():
|
||||
scans = await db.get_all_monitored_urls()
|
||||
for scan in scans:
|
||||
result = await analyze(scan.url, scan.mode)
|
||||
if has_changes(result, scan.last_result):
|
||||
await send_change_alert(scan, result)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Multi-Website Vergleich (P3, 1 Tag)
|
||||
|
||||
### Nutzen
|
||||
|
||||
"Wie steht mein Unternehmen im Vergleich zu 5 Wettbewerbern?"
|
||||
|
||||
### Implementierung
|
||||
|
||||
Frontend: Mehrere URLs eingeben → paralleler Scan → Vergleichstabelle:
|
||||
|
||||
```
|
||||
| Meine Firma | Opodo | Booking | Expedia |
|
||||
Datenschutzerkl. | ✓ | ✓ | ✓ | ✓ |
|
||||
Impressum | ✓ | ✗ (404) | ✓ | ✓ |
|
||||
Cookie-Banner | ✓ | ✓ | ✓ | ✗ |
|
||||
Google Fonts lokal | ✓ | ✗ | ✓ | ✗ |
|
||||
Kuendigungsbutton | ✓ | ✗ | n/a | n/a |
|
||||
Tracking vor Consent| ✗ | ✗ | ✓ | ✗ |
|
||||
Risiko-Score | 15/100 | 45/100 | 20/100 | 55/100 |
|
||||
```
|
||||
|
||||
**Endpoint:** `POST /api/compliance/agent/compare`
|
||||
**Body:** `{ "urls": ["url1", "url2", "url3"] }`
|
||||
|
||||
---
|
||||
|
||||
## Implementierungsreihenfolge
|
||||
|
||||
| Woche | Phase | Was | Ergebnis |
|
||||
|-------|-------|-----|----------|
|
||||
| 1 | Phase 0 | UCCA Score-Kalibrierung | Realistische Risiko-Scores |
|
||||
| 1 | Phase 1 | Control Relevance Filter | Keine False Positives mehr |
|
||||
| 2 | Phase 2 | Consent-Test (Playwright) | Killer-Feature, vor/nach Einwilligung |
|
||||
| 2 | Phase 3 | Registry 80+ Services | Umfassende Dienstleister-Erkennung |
|
||||
| 3 | Phase 4 | Scan beschleunigen | <60 Sekunden statt 3-5 Minuten |
|
||||
| 3 | Phase 5 | DB-Persistenz | Scan-Verlauf, keine verlorenen Ergebnisse |
|
||||
| 4 | Phase 6 | PDF-Export | Druckbare Reports fuer Management |
|
||||
| 4 | Phase 7 | Recurring Scans | Automatische Ueberwachung |
|
||||
| 5 | Phase 8 | Multi-Website Vergleich | Wettbewerber-Benchmark |
|
||||
| 6 | Phase 9 | Authenticated Testing | Login-Bereich pruefen (§312k, Art. 17, 20) |
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Authenticated Website Testing (P3, 2 Tage)
|
||||
|
||||
### Konzept
|
||||
|
||||
Ein DSB gibt seine eigenen Credentials im SDK ein. Playwright loggt sich ein
|
||||
und prueft den Kundenbereich auf Pflichtfunktionen:
|
||||
|
||||
### Pruefbare Rechte nach Login
|
||||
|
||||
| Pruefung | Rechtsgrundlage | Methode |
|
||||
|----------|----------------|---------|
|
||||
| Kuendigungsbutton (2 Klicks) | §312k BGB | Navigation suchen, Klicks zaehlen |
|
||||
| Konto loeschen | Art. 17 DSGVO | "Konto loeschen" Button suchen |
|
||||
| Daten exportieren | Art. 20 DSGVO | "Daten herunterladen" suchen |
|
||||
| Einwilligungen widerrufen | Art. 7(3) DSGVO | Consent-Einstellungen suchen |
|
||||
| Profildaten einsehen | Art. 15 DSGVO | Profil-/Kontobereich pruefen |
|
||||
|
||||
### Sicherheit
|
||||
|
||||
- Credentials werden NUR fuer die Dauer des Tests im Browser-Kontext gehalten
|
||||
- Kein Speichern in DB, kein Logging, kein Senden an Dritte
|
||||
- Nach Test: Browser-Kontext wird zerstoert, Credentials verworfen
|
||||
- HTTPS-only (kein HTTP-Login)
|
||||
|
||||
### Implementierung
|
||||
|
||||
- Erweiterung des `consent-tester` Service um Login-Flow
|
||||
- Neuer Tab im Frontend: "Authentifizierter Test"
|
||||
- Credential-Eingabe als einmalige Formularfelder (nicht gespeichert)
|
||||
- Screenshots als Belege fuer den Report
|
||||
|
||||
### Dateien
|
||||
|
||||
| Datei | LOC | Zweck |
|
||||
|-------|-----|-------|
|
||||
| `consent-tester/services/authenticated_scanner.py` | ~200 | Login + Kundenbereich-Checks |
|
||||
| `consent-tester/main.py` | +30 | Neuer /authenticated-scan Endpoint |
|
||||
| Frontend: AuthenticatedTestTab | ~150 | Credential-Eingabe + Ergebnis |
|
||||
|
||||
---
|
||||
|
||||
## Phase 10: Website-Scan auf Playwright umstellen (P3, 1-2 Tage)
|
||||
|
||||
### Problem
|
||||
|
||||
Der Website-Scan nutzt httpx (wie curl) — bekommt nur initiales HTML.
|
||||
SPAs (React, Angular, Vue) die Inhalte per JavaScript nachladen werden
|
||||
unvollstaendig gescannt. Opodo-Stil Seiten liefern nur Shell-HTML.
|
||||
|
||||
### Loesung
|
||||
|
||||
Website-Scanner auf Playwright umstellen — gleicher Headless Browser
|
||||
wie der Consent-Tester. Dann sieht der Scan ALLES was der Browser sieht.
|
||||
|
||||
| Technologie | Aktuell (httpx) | Nach Phase 10 (Playwright) |
|
||||
|------------|-----------------|---------------------------|
|
||||
| Statisches HTML | ✓ | ✓ |
|
||||
| WordPress | ✓ | ✓ |
|
||||
| React/Vue SPA | ✗ (nur Shell) | ✓ (rendert JS) |
|
||||
| Angular SSR | ✗/✓ | ✓ |
|
||||
| JS-heavy (Opodo) | ✗ | ✓ |
|
||||
|
||||
### Implementierung
|
||||
|
||||
- `consent-tester` Service um `/website-scan` Endpoint erweitern
|
||||
- Playwright navigiert zu jeder Seite, wartet auf JS, extrahiert HTML
|
||||
- Backend-Scanner ruft consent-tester statt httpx auf
|
||||
- Gleicher Output (DetectedService, ScanResult) — nur bessere Eingabedaten
|
||||
|
||||
## Investoren-Demo Szenario
|
||||
|
||||
Nach Phase 2 (Woche 2) koennen wir folgende Demo zeigen:
|
||||
|
||||
1. **URL eingeben:** `https://www.opodo.de`
|
||||
2. **Website-Scan:** 6 Seiten gescannt, Google Analytics + Fonts + GTM erkannt
|
||||
3. **SOLL/IST:** 3 Dienste NICHT in DSE dokumentiert → Art. 13 Verstoss
|
||||
4. **Consent-Test:** Google Analytics laedt VOR Einwilligung → §25 TDDDG Verstoss
|
||||
5. **Consent ablehnen:** Analytics laedt TROTZ Ablehnung → KRITISCH
|
||||
6. **Score:** MEDIUM (45/100) mit 5 Findings
|
||||
7. **Korrekturvorschlag:** Einbaufertige DSE-Textbausteine per Qwen
|
||||
8. **Email an DSB:** Formatierter HTML-Report mit Handlungsanweisung
|
||||
9. **Vergleich:** Opodo vs. Booking.com — wer ist besser aufgestellt?
|
||||
|
||||
Das demonstriert:
|
||||
- RAG funktioniert gegen echte Systeme (166k Controls)
|
||||
- LLM generiert juristische Textbausteine
|
||||
- Automatisierte Compliance-Pruefung in <60 Sekunden
|
||||
- Consent-Test den kein Wettbewerber hat
|
||||
@@ -0,0 +1,367 @@
|
||||
# ZeroClaw Agent-Architektur — Referenzdokumentation
|
||||
|
||||
Dieses Dokument beschreibt die vollstaendige Architektur des Compliance-Agenten,
|
||||
damit sie als Blaupause fuer weitere Agenten (z.B. Sales, HR, Finance) genutzt werden kann.
|
||||
|
||||
---
|
||||
|
||||
## 1. Ueberblick
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ FRONTEND (Next.js) │
|
||||
│ admin-compliance/app/sdk/agent/page.tsx │
|
||||
│ 5 Tabs: Quick | Scan | Cookie-Test | Vergleich | Login-Test │
|
||||
│ │
|
||||
│ Proxy-Routes: /api/sdk/v1/agent/* │
|
||||
│ → leiten an Backend weiter (vermeidet SSL-Cert-Probleme) │
|
||||
└──────────────────────┬──────────────────────────────────────────┘
|
||||
│ POST /api/sdk/v1/agent/{mode}
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ BACKEND (Python/FastAPI) │
|
||||
│ backend-compliance:8002 │
|
||||
│ │
|
||||
│ Orchestriert den gesamten Flow: │
|
||||
│ 1. Ruft Playwright-Service fuer Website-Crawling │
|
||||
│ 2. Erkennt Services via Regex (82+ Patterns) │
|
||||
│ 3. Ruft LLM fuer Klassifikation/Korrekturen │
|
||||
│ 4. Prueft Pflichtfelder (Art. 13, §355, §305ff) │
|
||||
│ 5. Sendet E-Mail-Report │
|
||||
│ │
|
||||
│ Routes: │
|
||||
│ - agent_analyze_routes.py (Schnellanalyse) │
|
||||
│ - agent_scan_routes.py (Website-Scan + DSI Discovery) │
|
||||
│ - agent_compare_routes.py (Multi-Website-Vergleich) │
|
||||
│ - agent_notification_routes.py (E-Mail) │
|
||||
│ - agent_history_routes.py (Scan-Verlauf + PDF) │
|
||||
└──────────┬──────────────────────┬───────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────────┐ ┌──────────────────────────────────────┐
|
||||
│ OLLAMA (LLM) │ │ CONSENT-TESTER (Playwright) │
|
||||
│ Mac Mini lokal │ │ consent-tester:8094 │
|
||||
│ │ │ │
|
||||
│ Qwen 3.5:35b-a3b │ │ Headless Chromium Browser: │
|
||||
│ Port: 11434 │ │ - /scan (3-Phasen Cookie-Test) │
|
||||
│ │ │ - /website-scan (JS-Rendering) │
|
||||
│ Endpunkte: │ │ - /dsi-discovery (Dokumentensuche) │
|
||||
│ /api/chat │ │ - /authenticated-scan (Login-Test) │
|
||||
│ /api/generate │ │ │
|
||||
│ /api/embeddings │ │ 20 Banner-Checks (rechtlich) │
|
||||
└─────────────────────┘ └──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Request-Flow (am Beispiel Website-Scan)
|
||||
|
||||
```
|
||||
User klickt "Scan starten" im Frontend
|
||||
│
|
||||
▼
|
||||
POST /api/sdk/v1/agent/scan { url, mode, recipient }
|
||||
│
|
||||
├── Next.js API Route (Proxy)
|
||||
│ admin-compliance/app/api/sdk/v1/agent/scan/route.ts
|
||||
│ → Leitet an backend-compliance:8002 weiter
|
||||
│ → Timeout: 5 Minuten
|
||||
│
|
||||
▼
|
||||
Backend: agent_scan_routes.py :: scan_website_endpoint()
|
||||
│
|
||||
├── Schritt 1: Playwright Website-Scan
|
||||
│ POST http://consent-tester:8094/website-scan
|
||||
│ → Chromium oeffnet URL, rendert JavaScript
|
||||
│ → Klickt Navigations-Menues, entdeckt Unterseiten
|
||||
│ → Gibt HTML aller Seiten zurueck (max 50 Seiten, 3 Min Timeout)
|
||||
│
|
||||
├── Schritt 1b: DSI Document Discovery
|
||||
│ POST http://consent-tester:8094/dsi-discovery
|
||||
│ → Findet alle rechtlichen Dokumente (DSI, AGB, Widerruf, Cookie)
|
||||
│ → Oeffnet Accordions, Tabs, Sidebars
|
||||
│ → Folgt Cross-Domain-Links (z.B. help.instagram.com)
|
||||
│ → Extrahiert Text aus jedem Dokument
|
||||
│ → Backend prueft Vollstaendigkeit (Art. 13 Checkliste)
|
||||
│
|
||||
├── Schritt 2: Service-Erkennung (deterministisch, kein LLM)
|
||||
│ service_registry.py: 82+ Regex-Patterns gegen HTML
|
||||
│ → Google Analytics, Facebook Pixel, Stripe, Hotjar, etc.
|
||||
│ → Jeder Service hat: Kategorie, Anbieter, Land, Rechtsgrundlage
|
||||
│
|
||||
├── Schritt 3: DSE-Extraktion (LLM)
|
||||
│ POST http://ollama:11434/api/generate
|
||||
│ Prompt: "Extrahiere alle erwahnten Dienste aus dieser DSE..."
|
||||
│ → Qwen 3.5 liest den DSE-Text und gibt JSON zurueck
|
||||
│ → Fallback: Regex-Suche wenn LLM fehlschlaegt
|
||||
│
|
||||
├── Schritt 4: SOLL/IST-Vergleich
|
||||
│ SOLL = Services aus DSE (was dokumentiert ist)
|
||||
│ IST = Services auf Website (was tatsaechlich laeuft)
|
||||
│ → "undocumented": auf Website, nicht in DSE (Verstoss!)
|
||||
│ → "documented": beides OK
|
||||
│ → "outdated": in DSE, nicht mehr auf Website
|
||||
│
|
||||
├── Schritt 5: Pflichtfeld-Pruefung
|
||||
│ mandatory_content_checker.py: 9 Art. 13 DSGVO Felder
|
||||
│ legal_basis_validator.py: Rechtsgrundlage korrekt?
|
||||
│ dsi_document_checker.py: Jedes gefundene Dokument pruefen
|
||||
│
|
||||
├── Schritt 6: Korrekturen generieren (LLM, nur bei Findings)
|
||||
│ POST http://ollama:11434/api/generate
|
||||
│ Prompt: "Erstelle DSE-Textbaustein fuer Google Analytics..."
|
||||
│ → Qwen 3.5 generiert einbaufertigen Text
|
||||
│
|
||||
├── Schritt 7: E-Mail senden
|
||||
│ smtp_sender.py → Mailpit (SMTP :1025)
|
||||
│ HTML-Report mit allen Findings + Korrekturen
|
||||
│
|
||||
└── Response zurueck an Frontend
|
||||
ScanResponse { pages_scanned, services[], findings[],
|
||||
discovered_documents[], summary }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Komponenten im Detail
|
||||
|
||||
### 3.1 Frontend (Next.js)
|
||||
|
||||
**Seite:** `admin-compliance/app/sdk/agent/page.tsx`
|
||||
**Hooks:** `admin-compliance/app/sdk/agent/_hooks/`
|
||||
**Komponenten:** `admin-compliance/app/sdk/agent/_components/`
|
||||
|
||||
| Komponente | Aufgabe |
|
||||
|---|---|
|
||||
| ScanResult.tsx | Service-Tabelle, Findings, PDF-Download |
|
||||
| ConsentTestResult.tsx | 3-Phasen-Anzeige + Banner-Checks |
|
||||
| CompareResult.tsx | Side-by-Side Vergleich |
|
||||
| AuthTestResult.tsx | 5 Login-Checks |
|
||||
| TextReference.tsx | Originaltext + Korrekturvorschlag |
|
||||
| FollowUpQuestions.tsx | Ja/Nein Fragen |
|
||||
|
||||
**Proxy-Pattern:** Jeder API-Call geht ueber eine Next.js API Route
|
||||
(`app/api/sdk/v1/agent/*/route.ts`) die serverseitig an das Backend weiterleitet.
|
||||
Das vermeidet CORS- und SSL-Probleme.
|
||||
|
||||
### 3.2 Backend (Python/FastAPI)
|
||||
|
||||
**Service:** `backend-compliance:8002`
|
||||
**Prefix:** `/api/compliance/agent/`
|
||||
|
||||
| Route-Datei | Endpunkt | Aufgabe |
|
||||
|---|---|---|
|
||||
| agent_analyze_routes.py | POST /analyze | Schnellanalyse (1 URL) |
|
||||
| agent_scan_routes.py | POST /scan | Deep Scan + DSI Discovery |
|
||||
| agent_compare_routes.py | POST /compare | Multi-URL Vergleich |
|
||||
| agent_notification_routes.py | POST /notify | E-Mail senden |
|
||||
| agent_history_routes.py | GET/POST /scans | Scan-Verlauf |
|
||||
| agent_recurring_routes.py | */monitored-urls | Wiederkehrende Scans |
|
||||
|
||||
**Wichtiges Design-Prinzip:** Das Backend orchestriert, macht aber keine
|
||||
Browser-Operationen. Alles was einen Browser braucht → consent-tester.
|
||||
|
||||
### 3.3 Consent-Tester (Playwright/FastAPI)
|
||||
|
||||
**Service:** `consent-tester:8094`
|
||||
**Dockerfile:** Chromium als appuser installiert (Permission-Fix)
|
||||
|
||||
| Endpunkt | Aufgabe | Dateien |
|
||||
|---|---|---|
|
||||
| POST /scan | 3-Phasen Cookie-Test | consent_scanner.py, banner_text_checker.py, banner_advanced_checks.py |
|
||||
| POST /website-scan | JS-gerendertes Crawling | playwright_scanner.py |
|
||||
| POST /dsi-discovery | Dokumenten-Suche | dsi_discovery.py |
|
||||
| POST /authenticated-scan | Login + Rechte-Check | authenticated_scanner.py |
|
||||
| GET /health | Healthcheck | main.py |
|
||||
|
||||
**20 Banner-Checks** in 3 Dateien:
|
||||
- banner_text_checker.py (Checks 1-11)
|
||||
- banner_advanced_checks.py (Checks 12-20)
|
||||
|
||||
### 3.4 LLM (Ollama)
|
||||
|
||||
**Modell:** Qwen 3.5:35b-a3b (23.9 GB, lokal auf Mac Mini)
|
||||
**Port:** 11434 (erreichbar als `host.docker.internal:11434`)
|
||||
|
||||
**Zwei Aufruf-Patterns:**
|
||||
|
||||
```python
|
||||
# Pattern 1: /api/generate (einfache Completion)
|
||||
resp = await client.post(f"{OLLAMA_URL}/api/generate", json={
|
||||
"model": "qwen3.5:35b-a3b",
|
||||
"prompt": "Erstelle einen DSE-Textbaustein...",
|
||||
"stream": False,
|
||||
})
|
||||
text = resp.json()["response"]
|
||||
|
||||
# Pattern 2: /api/chat (Chat mit System-Prompt)
|
||||
resp = await client.post(f"{OLLAMA_URL}/api/chat", json={
|
||||
"model": "qwen3.5:35b-a3b",
|
||||
"messages": [
|
||||
{"role": "system", "content": "Du bist ein DSGVO-Experte..."},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
"stream": False,
|
||||
"format": "json", # Erzwingt JSON-Ausgabe
|
||||
})
|
||||
text = resp.json()["message"]["content"]
|
||||
```
|
||||
|
||||
**Think-Mode beachten:** Qwen 3.5 gibt `<think>...</think>` Tags aus.
|
||||
Diese muessen aus der Antwort entfernt werden:
|
||||
```python
|
||||
raw = re.sub(r"<think>.*?</think>", "", raw, flags=re.DOTALL).strip()
|
||||
```
|
||||
|
||||
### 3.5 Soul Files (Agent-Persoenlichkeiten)
|
||||
|
||||
**Pfad:** `admin-compliance/agent-core/soul/`
|
||||
|
||||
| Datei | Agent | Aufgabe |
|
||||
|---|---|---|
|
||||
| compliance-advisor.soul.md | Compliance Advisor | Rechtsfragen beantworten (RAG) |
|
||||
| drafting-agent.soul.md | Drafting Agent | DSGVO-Dokumente generieren |
|
||||
|
||||
**Aufbau einer Soul-Datei:**
|
||||
1. Identitaet (Wer bin ich?)
|
||||
2. Kompetenzbereich (Was kann ich?)
|
||||
3. RAG-Nutzung (Welche Quellen?)
|
||||
4. Kommunikationsstil (Wie antworte ich?)
|
||||
5. Einschraenkungen (Was darf ich nicht?)
|
||||
6. Produktwissen (Features des Tools)
|
||||
7. FAQ (Haeufige Fragen + Antworten)
|
||||
8. Eskalation (Wann verweise ich weiter?)
|
||||
|
||||
---
|
||||
|
||||
## 4. Blaupause: Neuen Agenten erstellen
|
||||
|
||||
### Schritt 1: Soul-Datei erstellen
|
||||
|
||||
```markdown
|
||||
# Mein Agent
|
||||
|
||||
## Identitaet
|
||||
Du bist der [Name]-Agent. Du hilfst bei [Aufgabe].
|
||||
|
||||
## Kompetenzbereich
|
||||
- [Thema 1]
|
||||
- [Thema 2]
|
||||
|
||||
## Kommunikationsstil
|
||||
- Sachlich, verstaendlich
|
||||
- Quellenangaben
|
||||
|
||||
## Einschraenkungen
|
||||
- Keine [X]-Beratung
|
||||
```
|
||||
|
||||
### Schritt 2: Backend-Route erstellen
|
||||
|
||||
```python
|
||||
# backend-compliance/compliance/api/mein_agent_routes.py
|
||||
from fastapi import APIRouter
|
||||
router = APIRouter(prefix="/compliance/mein-agent", tags=["mein-agent"])
|
||||
|
||||
@router.post("/analyze")
|
||||
async def analyze(req: AnalyzeRequest):
|
||||
# 1. Daten holen (Playwright, API, DB)
|
||||
# 2. LLM aufrufen (Ollama)
|
||||
# 3. Ergebnis aufbereiten
|
||||
# 4. E-Mail senden
|
||||
return result
|
||||
```
|
||||
|
||||
### Schritt 3: Frontend-Page erstellen
|
||||
|
||||
```
|
||||
admin-compliance/app/sdk/mein-agent/
|
||||
├── page.tsx # Hauptseite mit Tabs
|
||||
├── _hooks/ # State + API-Calls
|
||||
└── _components/ # Ergebnis-Anzeige
|
||||
```
|
||||
|
||||
### Schritt 4: Next.js Proxy-Route
|
||||
|
||||
```typescript
|
||||
// admin-compliance/app/api/sdk/v1/mein-agent/[[...path]]/route.ts
|
||||
const BACKEND_URL = 'http://backend-compliance:8002'
|
||||
|
||||
export async function POST(req, { params }) {
|
||||
const path = (await params).path?.join('/') || ''
|
||||
const resp = await fetch(`${BACKEND_URL}/api/compliance/mein-agent/${path}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: await req.text(),
|
||||
})
|
||||
return NextResponse.json(await resp.json())
|
||||
}
|
||||
```
|
||||
|
||||
### Schritt 5: In docker-compose.yml registrieren
|
||||
|
||||
Der Agent laeuft als Teil des Backend-Services (kein eigener Container noetig,
|
||||
es sei denn er braucht Playwright — dann eigenen Service wie consent-tester).
|
||||
|
||||
---
|
||||
|
||||
## 5. Timeouts
|
||||
|
||||
| Komponente | Timeout | Grund |
|
||||
|---|---|---|
|
||||
| Frontend → Backend Proxy | 300s (5 Min) | Scan kann lange dauern |
|
||||
| Backend → Playwright | 180s (3 Min) | Browser-Rendering + Crawling |
|
||||
| Backend → Ollama LLM | 180s (3 Min) | Grosse Modelle sind langsam |
|
||||
| Playwright → Website | 20s pro Seite | Einzelne Seite laden |
|
||||
| Exhaustive Crawl | 180-300s | Safety-Limit fuer rekursives Crawling |
|
||||
|
||||
---
|
||||
|
||||
## 6. Environment-Variablen
|
||||
|
||||
| Variable | Default | Beschreibung |
|
||||
|---|---|---|
|
||||
| OLLAMA_URL | http://host.docker.internal:11434 | Ollama LLM Endpunkt |
|
||||
| OLLAMA_MODEL | qwen3.5:35b-a3b | Standard-Modell |
|
||||
| BACKEND_URL | http://backend-compliance:8002 | Backend API |
|
||||
| CONSENT_SERVICE_URL | http://consent-tester:8094 | Playwright-Service |
|
||||
| RAG_SERVICE_URL | http://rag-service:8097 | RAG/Embedding Service |
|
||||
| QDRANT_URL | https://qdrant-dev.breakpilot.ai | Vektor-Datenbank |
|
||||
| SMTP_HOST | mailpit | E-Mail Server |
|
||||
| SMTP_PORT | 1025 | SMTP Port |
|
||||
|
||||
---
|
||||
|
||||
## 7. Dateien (Referenz)
|
||||
|
||||
### Backend
|
||||
| Datei | LOC | Aufgabe |
|
||||
|---|---|---|
|
||||
| agent_scan_routes.py | 448 | Scan-Orchestrierung |
|
||||
| agent_scan_helpers.py | 109 | Summary + Korrekturen |
|
||||
| agent_analyze_routes.py | 309 | Schnellanalyse |
|
||||
| agent_compare_routes.py | 94 | Multi-Vergleich |
|
||||
| agent_notification_routes.py | 111 | E-Mail |
|
||||
| service_registry.py | 508 | 82+ Service-Patterns |
|
||||
| dsi_document_checker.py | 229 | Dokument-Checklisten |
|
||||
| mandatory_content_checker.py | 274 | Art. 13 Pflichtfelder |
|
||||
| legal_basis_validator.py | 155 | Rechtsgrundlagen |
|
||||
|
||||
### Consent-Tester
|
||||
| Datei | LOC | Aufgabe |
|
||||
|---|---|---|
|
||||
| main.py | 321 | FastAPI + Endpunkte |
|
||||
| consent_scanner.py | 213 | 3-Phasen-Test |
|
||||
| banner_text_checker.py | 407 | Banner-Checks 1-11 |
|
||||
| banner_advanced_checks.py | 396 | Banner-Checks 12-20 |
|
||||
| dsi_discovery.py | 488 | Dokumenten-Suche |
|
||||
| playwright_scanner.py | 266 | Website-Crawling |
|
||||
| authenticated_scanner.py | 230 | Login-Tests |
|
||||
|
||||
### Frontend
|
||||
| Datei | LOC | Aufgabe |
|
||||
|---|---|---|
|
||||
| agent/page.tsx | 207 | 5-Tab Agent UI |
|
||||
| ScanResult.tsx | 241 | Scan-Ergebnis |
|
||||
| ConsentTestResult.tsx | 207 | Cookie-Test-Ergebnis |
|
||||
| TextReference.tsx | 108 | Korrekturvorschlaege |
|
||||
@@ -0,0 +1,114 @@
|
||||
# Compliance Agent — Dokumentation
|
||||
|
||||
## Uebersicht
|
||||
|
||||
Der Compliance Agent analysiert Websites und Dokumente automatisch auf DSGVO-Konformitaet.
|
||||
Er kombiniert Website-Scanning, LLM-Analyse, Control Library und Playwright Browser-Tests
|
||||
zu einem umfassenden Compliance-Audit.
|
||||
|
||||
## 5 Analyse-Modi
|
||||
|
||||
### 1. Schnellanalyse
|
||||
Einzelne URL klassifizieren und bewerten.
|
||||
- Qwen klassifiziert Dokumenttyp (DSE, Cookie-Banner, AGB, Impressum)
|
||||
- LLM extrahiert Intake-Flags (14 Kategorien)
|
||||
- UCCA Assessment bewertet Risiko
|
||||
- Relevance Filter entfernt False-Positive Controls
|
||||
- Email-Benachrichtigung an zustaendige Rolle
|
||||
|
||||
### 2. Website-Scan
|
||||
Multi-Page Crawl mit Dienstleister-Abgleich.
|
||||
- Playwright-Browser scannt 5-15 Seiten (JS-Rendering, Menue-Klicks)
|
||||
- 82+ Dienste erkannt (Tracking, CDN, Chatbots, Payment, Marketing)
|
||||
- SOLL/IST-Abgleich: DSE-Text vs. tatsaechlich eingebundene Dienste
|
||||
- Pflichtinhalte-Check: Art. 13 DSGVO (9 Felder) + §5 TMG (5 Felder)
|
||||
- Textblock-Referenzierung: Originaltext, Position, Korrekturvorschlag
|
||||
- Lit-Mapping: Prueft ob korrekte Rechtsgrundlage (lit. a-f) verwendet wird
|
||||
|
||||
### 3. Cookie-Test
|
||||
3-Phasen Consent-Test mit echtem Chromium-Browser.
|
||||
- Phase A: Was laedt VOR Einwilligung? (§25 TDDDG Verstoss)
|
||||
- Phase B: Was laedt NACH Ablehnung? (KRITISCH wenn Tracking weiterlaeuft)
|
||||
- Phase C: Was laedt NACH Zustimmung? (Abgleich mit Cookie-Policy)
|
||||
- Phase D-F: Einzelne Kategorien testen (Statistik, Marketing, Funktional)
|
||||
- 10 CMP-spezifische Selektoren (Cookiebot, OneTrust, Didomi, etc.)
|
||||
|
||||
### 4. Vergleich
|
||||
2-5 Websites parallel scannen und Compliance vergleichen.
|
||||
- Vergleichstabelle: Risiko, Findings, Services, Impressum, Cookie-Banner
|
||||
|
||||
### 5. Login-Test
|
||||
Kundenbereich nach Login pruefen.
|
||||
- §312k BGB: Kuendigungsbutton (2 Klicks)
|
||||
- Art. 17 DSGVO: Konto loeschen
|
||||
- Art. 20 DSGVO: Daten exportieren
|
||||
- Art. 7(3) DSGVO: Einwilligungen widerrufen
|
||||
- Art. 15 DSGVO: Profildaten einsehen
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Backend (Port 8002)
|
||||
|
||||
| Method | Endpoint | Beschreibung |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/api/compliance/agent/analyze` | Schnellanalyse |
|
||||
| POST | `/api/compliance/agent/scan` | Website-Scan |
|
||||
| POST | `/api/compliance/agent/notify` | Email senden |
|
||||
| POST | `/api/compliance/agent/scans` | Scan speichern |
|
||||
| GET | `/api/compliance/agent/scans` | Scan-Verlauf |
|
||||
| POST | `/api/compliance/agent/scans/pdf` | PDF-Export |
|
||||
| POST | `/api/compliance/agent/compare` | Multi-Website Vergleich |
|
||||
| POST | `/api/compliance/agent/monitored-urls` | URL zur Ueberwachung |
|
||||
| POST | `/api/compliance/agent/run-scheduled` | Scheduled Scans triggern |
|
||||
|
||||
### Consent-Tester (Port 8094)
|
||||
|
||||
| Method | Endpoint | Beschreibung |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/scan` | 3-Phasen Cookie-Test |
|
||||
| POST | `/website-scan` | Playwright Website-Scan |
|
||||
| POST | `/authenticated-scan` | Login-Test |
|
||||
| GET | `/health` | Health Check |
|
||||
|
||||
## Service-Registry
|
||||
|
||||
82+ Dienste in 15 Kategorien:
|
||||
Tracking, Marketing, Newsletter, CDN, Chatbots, Payment, Heatmaps,
|
||||
A/B Testing, Tag Manager, Push, Video, Social, Error Tracking, CRM, Accessibility.
|
||||
|
||||
Datei: `backend-compliance/compliance/services/service_registry.py`
|
||||
|
||||
## Pre-Launch vs. Post-Launch
|
||||
|
||||
| Modus | Tonfall | Empfehlung |
|
||||
|-------|---------|------------|
|
||||
| Pre-Launch | "Vor Veroeffentlichung korrigieren" | Einbaufertige DSE-Textbausteine |
|
||||
| Post-Launch | "ACHTUNG: Oeffentlich sichtbar!" | Sofortige Nachbesserung |
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
Browser (Frontend)
|
||||
|
|
||||
├── /sdk/agent (Next.js, 5 Tabs)
|
||||
|
|
||||
├── Next.js API Proxies (/api/sdk/v1/agent/*)
|
||||
| |
|
||||
| ├── Backend (FastAPI, Port 8002)
|
||||
| | ├── agent_analyze_routes.py
|
||||
| | ├── agent_scan_routes.py (+ Playwright integration)
|
||||
| | ├── agent_history_routes.py
|
||||
| | ├── agent_recurring_routes.py
|
||||
| | └── agent_compare_routes.py
|
||||
| |
|
||||
| └── Consent-Tester (FastAPI + Playwright, Port 8094)
|
||||
| ├── consent_scanner.py (3-Phasen + Kategorien)
|
||||
| ├── playwright_scanner.py (Website-Scan)
|
||||
| ├── authenticated_scanner.py (Login-Test)
|
||||
| ├── banner_detector.py (10 CMPs)
|
||||
| ├── category_tester.py (Kategorie-Toggles)
|
||||
| └── script_analyzer.py (Service-Erkennung)
|
||||
|
|
||||
├── Qwen 3.5:35b-a3b (Ollama, Port 11434)
|
||||
└── Mailpit (SMTP 1025, Web 8025)
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# Compliance Agent — Reference Test Cases
|
||||
|
||||
Reale Befunde von echten Websites. Jeder Case dokumentiert:
|
||||
- Was gefunden wurde
|
||||
- Welche Rechtsgrundlage verletzt ist
|
||||
- Was der Agent erkennen sollte
|
||||
- Wie die Korrektur aussehen muss
|
||||
|
||||
## Cases
|
||||
|
||||
| Case | Website | Typ | Schwere |
|
||||
|------|---------|-----|---------|
|
||||
| [EUIPO Registration + Chat](euipo-registration-consent.md) | login.euipo.europa.eu + euipo.europa.eu | 10 Findings: Consent-Text, Koppelungsverbot, Unblu Chat, Dismiss-as-Consent, Dark Pattern | HIGH |
|
||||
@@ -0,0 +1,196 @@
|
||||
# Test Case: EUIPO Registration — Consent-Formulierung
|
||||
|
||||
**Quelle:** EU-Behoerde (Amt der Europaeischen Union fuer geistiges Eigentum)
|
||||
**URL:** https://login.euipo.europa.eu (Registrierungsseite)
|
||||
**Entdeckt:** 2026-05-04
|
||||
**Typ:** Consent-Text-Fehler + Koppelungsverbot
|
||||
**Schwere:** HIGH
|
||||
|
||||
---
|
||||
|
||||
## Befund
|
||||
|
||||
Auf der Registrierungsseite des EUIPO steht (vollstaendiger Text):
|
||||
|
||||
> "Wenn Sie auf Anmelden klicken, stimmen Sie unseren zu
|
||||
> Nutzungsbedingungen und Datenschutz-Bestimmungen gelesen und verstanden
|
||||
> Registrieren
|
||||
> Haben Sie bereits ein Konto? Anmelden
|
||||
> Datenschutz-Bestimmungen gelesen und verstanden
|
||||
> Nutzungsbedingungen
|
||||
> Deutsch"
|
||||
|
||||
Der Text "Datenschutz-Bestimmungen gelesen und verstanden" und
|
||||
"Nutzungsbedingungen" erscheinen als separate Links unterhalb des
|
||||
Registrieren-Buttons — vermutlich soll der obere Satz darauf verweisen,
|
||||
aber die Verknuepfung ist kaputt (Links nach dem Button statt inline).
|
||||
|
||||
### Fehler
|
||||
|
||||
| # | Typ | Beschreibung | Rechtsgrundlage |
|
||||
|---|-----|-------------|-----------------|
|
||||
| 1 | Sprachfehler | Satz grammatisch unvollstaendig ("stimmen Sie unseren zu" ergibt keinen Sinn) | Art. 12(1) DSGVO (klare und verstaendliche Sprache) |
|
||||
| 2 | Zwangs-Consent | Login = automatische Zustimmung, kein separater Opt-in | Art. 7(4) DSGVO (Koppelungsverbot) |
|
||||
| 3 | Keine Ablehnung | Kein Button um NICHT zuzustimmen (nur Login oder Seite verlassen) | Art. 7(3) DSGVO (Widerruf so einfach wie Erteilung) |
|
||||
| 4 | Keine Granularitaet | Nutzungsbedingungen und Datenschutz werden in einer Zustimmung zusammengefasst | EDPB Guidelines 05/2020 Rn. 43 (Granularitaet) |
|
||||
| 5 | Kein aktiver Consent | Kein Checkbox oder aktive Handlung — blosse Nutzung gilt als Zustimmung | EuGH C-673/17 Planet49 (aktive Einwilligung) |
|
||||
| 6 | Kaputtes Link-Layout | Links zu DSE und Nutzungsbedingungen erscheinen NACH dem Registrieren-Button statt inline im Consent-Text — der Satz oben verweist ins Leere | Art. 12(1) DSGVO (leicht zugaenglich) |
|
||||
| 7 | Uebersetzungsfehler | Offensichtlich maschinell aus EN uebersetzt ("stimmen Sie unseren zu" ist kein korrektes Deutsch) — widerspricht Anforderung an Muttersprache des Nutzers | Art. 12(1) DSGVO (klare und einfache Sprache) |
|
||||
| 8 | Unblu Chat Drittanbieter-DSE | Chat-Consent verweist auf Datenschutzerklaerung von unblu.com (Drittanbieter) statt auf EUIPO-eigene Cookie-/Chat-Richtlinie. EUIPO ist Verantwortlicher (Art. 4 Nr. 7 DSGVO), muss eigene DSE bereitstellen. | Art. 13 DSGVO (Informationspflichten), Art. 26 DSGVO (gemeinsame Verantwortlichkeit) |
|
||||
| 9 | Unblu Chat Cookies ohne eigene Rechtsgrundlage | "Unblu-Cookies muessen heruntergeladen werden" — EUIPO delegiert Cookie-Einwilligung an Drittanbieter-DSE statt eigene Rechtsgrundlage zu benennen | § 25 TDDDG / Art. 5(3) ePrivacy-RL |
|
||||
| 10 | Dismiss-by-Click = Consent | Klick neben das Chat-Consent-Fenster wird als "Akzeptieren" gewertet. Versehentlicher Klick ist KEINE aktive Einwilligung. Modal muss echte Wahl erzwingen. | EuGH C-673/17 Planet49 (aktive Handlung), Art. 7(1) DSGVO (Nachweispflicht) |
|
||||
|
||||
---
|
||||
|
||||
## Befund 2: Unblu Chat Consent (nach Login)
|
||||
|
||||
Nach Registrierung und Login erscheint ein Chat-Fenster:
|
||||
|
||||
> "Personenbezogene Daten werden gemaess der Datenschutzerklaerung fuer den Online-Chat
|
||||
> erhoben und verarbeitet. Fuer den Online-Chat muessen Unblu-Cookies heruntergeladen
|
||||
> werden, damit er ordnungsgemaess funktioniert und damit die Chat-Operatoren wissen,
|
||||
> wann Sie zuletzt per Chat mit dem Amt kommuniziert haben. Diese Cookies unterliegen
|
||||
> den Datenschutzbestimmungen des Unblu Chats und werden nur eingesetzt, wenn Sie sich
|
||||
> fuer die Nutzung des Chats entscheiden."
|
||||
>
|
||||
> [Abbrechen] [Akzeptieren]
|
||||
|
||||
### Was positiv ist:
|
||||
- Es gibt tatsaechlich einen "Abbrechen"-Button (besser als die Registrierung)
|
||||
- Der Text erklaert den Zweck der Cookies (Chat-Funktionalitaet + Verlauf)
|
||||
|
||||
### Was falsch ist:
|
||||
|
||||
**1. Drittanbieter-DSE statt eigener:**
|
||||
Die Datenschutzerklaerung verlinkt auf unblu.com — aber EUIPO ist der Verantwortliche
|
||||
fuer die Datenverarbeitung, nicht Unblu. Unblu ist Auftragsverarbeiter. Die EUIPO muesste
|
||||
eine eigene Chat-Datenschutzerklaerung haben die beschreibt:
|
||||
- Welche Daten erhoben werden
|
||||
- Rechtsgrundlage (Art. 6(1)(a) Einwilligung)
|
||||
- Speicherdauer
|
||||
- Rechte des Betroffenen
|
||||
- Kontakt zum DSB
|
||||
|
||||
**2. Klick daneben = Akzeptieren:**
|
||||
Das Chat-Fenster ist NICHT modal — ein Klick auf den Hintergrund neben dem Fenster
|
||||
schliesst es UND wertet dies als Einwilligung. Das verletzt:
|
||||
- **Art. 7(1) DSGVO**: Einwilligung muss nachweisbar sein (versehentlicher Klick ist kein Nachweis)
|
||||
- **EuGH Planet49**: Einwilligung erfordert eine eindeutige bestaetigende Handlung
|
||||
- **EDPB Guidelines 05/2020 Rn. 77**: "Silence, pre-ticked boxes or inactivity should not constitute consent"
|
||||
|
||||
Ein Klick neben ein Fenster ist "inactivity" im Sinne der EDPB-Leitlinien — keine
|
||||
aktive Entscheidung.
|
||||
|
||||
**3. "muessen heruntergeladen werden":**
|
||||
Die Formulierung suggeriert technische Notwendigkeit ("muessen"), obwohl die Cookies
|
||||
fuer den Zweck "wann Sie zuletzt per Chat kommuniziert haben" nicht technisch notwendig
|
||||
sind. Das ist eine Dunkle-Muster-Formulierung die den Nutzer zur Zustimmung draengt.
|
||||
|
||||
---
|
||||
|
||||
## Erwartete Agent-Erkennung
|
||||
|
||||
Der Compliance Agent sollte folgende Checks ausfuehren:
|
||||
|
||||
### 1. Consent-Text-Analyse (consent_scanner.py)
|
||||
- `banner_text_violations`: Grammatisch fehlerhafte Consent-Formulierung erkennen
|
||||
- Severity: HIGH
|
||||
- Finding: "Consent-Text ist grammatisch unvollstaendig und nicht verstaendlich (Art. 12(1) DSGVO)"
|
||||
|
||||
### 2. Koppelungs-Check (authenticated_scanner.py)
|
||||
- Login-Button darf nicht gleichzeitig Consent erteilen
|
||||
- Finding: "Einwilligung wird an Registrierung/Login gekoppelt (Art. 7(4) DSGVO)"
|
||||
|
||||
### 3. Ablehn-Button-Check (consent_scanner.py)
|
||||
- `reject_button_check`: Kein gleichwertiger Ablehn-Button vorhanden
|
||||
- Finding: "Keine Moeglichkeit die Einwilligung abzulehnen"
|
||||
|
||||
### 4. Granularitaets-Check (NEU — zu implementieren)
|
||||
- Nutzungsbedingungen ≠ Datenschutz-Einwilligung
|
||||
- Muessen separat zustimmbar sein
|
||||
- Finding: "Nutzungsbedingungen und Datenschutz in einer Zustimmung zusammengefasst"
|
||||
|
||||
### 5. Drittanbieter-DSE-Check (NEU — zu implementieren)
|
||||
- Consent-Dialog verweist auf DSE eines Drittanbieters statt auf eigene DSE
|
||||
- Pruefe ob href in Consent-Text auf andere Domain zeigt als die aktuelle Website
|
||||
- Finding: "Consent verweist auf Datenschutzerklaerung von {domain} — Verantwortlicher
|
||||
muss eigene DSE bereitstellen (Art. 13 DSGVO)"
|
||||
|
||||
### 6. Modal-Dismiss-Check (NEU — zu implementieren)
|
||||
- Pruefe ob Consent-Dialog durch Klick auf Hintergrund geschlossen wird
|
||||
- Wenn dismiss = accept → Verstoss
|
||||
- Playwright: Klick auf Overlay-Backdrop, pruefe ob Consent gesetzt wurde
|
||||
- Finding: "Consent-Dialog kann durch Klick neben das Fenster geschlossen werden,
|
||||
was als Einwilligung gewertet wird (Art. 7(1) DSGVO, Planet49)"
|
||||
|
||||
### 7. Dark-Pattern-Sprache (NEU — zu implementieren)
|
||||
- "muessen heruntergeladen werden" suggeriert technische Notwendigkeit
|
||||
- Pruefe auf Formulierungen die Zwang suggerieren: "muessen", "erforderlich",
|
||||
"notwendig" fuer nicht-essentielle Cookies
|
||||
- Finding: "Formulierung suggeriert technische Notwendigkeit fuer nicht-essentielle
|
||||
Cookies (Dark Pattern, EDPB Guidelines 05/2020 Rn. 70)"
|
||||
|
||||
---
|
||||
|
||||
## Korrekturvorschlag
|
||||
|
||||
### Korrekte Formulierung
|
||||
|
||||
```
|
||||
☐ Ich habe die Nutzungsbedingungen gelesen und stimme ihnen zu. (Pflichtfeld)
|
||||
|
||||
☐ Ich habe die Datenschutzerklaerung gelesen und erklaere mich mit der
|
||||
Verarbeitung meiner personenbezogenen Daten gemaess Art. 6(1)(a) DSGVO
|
||||
einverstanden. (Freiwillig — Registrierung auch ohne moeglich)
|
||||
|
||||
[Registrieren] [Abbrechen]
|
||||
```
|
||||
|
||||
### Warum korrekt:
|
||||
- Zwei separate Checkboxen (Granularitaet)
|
||||
- Aktive Handlung noetig (Checkbox ankreuzen, nicht nur Button klicken)
|
||||
- Datenschutz-Consent ist freiwillig (Koppelungsverbot)
|
||||
- Klare Sprache, vollstaendige Saetze
|
||||
- Abbrechen-Button als Alternativhandlung
|
||||
|
||||
---
|
||||
|
||||
## Technische Implementierung als Agent-Check
|
||||
|
||||
### Neuer Check: `consent_text_quality`
|
||||
|
||||
```python
|
||||
# In consent_scanner.py oder neuer Check
|
||||
CONSENT_TEXT_PATTERNS = [
|
||||
# Zwangs-Consent bei Login/Registrierung
|
||||
r"(?:klicken|anmelden|registrieren).*stimmen\s+(?:Sie|sie|du).*zu",
|
||||
r"(?:login|sign.?up|register).*(?:agree|accept|consent)",
|
||||
# Grammatisch unvollstaendige Saetze
|
||||
r"stimmen\s+(?:Sie|sie|du)\s+unseren?\s+zu",
|
||||
# Fehlende Granularitaet
|
||||
r"(?:nutzungsbedingungen|terms).*(?:und|and).*(?:datenschutz|privacy).*(?:zu|agree|accept)",
|
||||
]
|
||||
|
||||
MISSING_ELEMENTS = [
|
||||
"checkbox", # Aktive Zustimmung (nicht nur Button)
|
||||
"button.*ablehnen", # Ablehn-Option
|
||||
"button.*cancel", # Abbrechen-Option
|
||||
]
|
||||
```
|
||||
|
||||
### Neuer Check: `coupling_prohibition`
|
||||
|
||||
Prueft ob Einwilligung an Registrierung/Login/Kauf gekoppelt ist:
|
||||
- Login-Button text enthalt "zustimmen/agree/accept" → Koppelungsverbot
|
||||
- Kein separater Consent-Checkbox vor Login-Button → Koppelungsverbot
|
||||
- Einwilligung als Pflichtfeld bei Registrierung → Koppelungsverbot (wenn Service auch ohne nutzbar)
|
||||
|
||||
---
|
||||
|
||||
## Relevanz
|
||||
|
||||
Dieser Case ist besonders relevant weil:
|
||||
1. **EU-Behoerde** — wenn selbst das EUIPO es falsch macht, wie sollen KMUs es richtig machen?
|
||||
2. **OIDC-Registrierung** — sehr haeufiges Pattern bei SaaS-Produkten
|
||||
3. **Mehrsprachig** — der deutsche Text ist offensichtlich schlecht uebersetzt
|
||||
4. **Systemisch** — WSO2 Identity Server Templates enthalten dieses Pattern standardmaessig
|
||||
Reference in New Issue
Block a user