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:
Benjamin Admin
2026-05-11 11:44:20 +02:00
175 changed files with 20063 additions and 1283 deletions
@@ -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`
+560
View File
@@ -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
+367
View File
@@ -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 |
+114
View File
@@ -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)
```
+13
View File
@@ -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