- Migration 086: compliance_agent_scans table (findings, services, corrections)
- agent_history_routes.py: POST /scans (save), GET /scans (list), GET /scans/{id}
- Scan results survive page reloads and can be reviewed later
- Phase 10 (Playwright website scanner) added to product roadmap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
18 KiB
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.
# 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-Extraktionai-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:
- Neues
relevance_conditionsJSONB-Feld aufcanonical_controls - Top-20 generische Controls mit Keywords versehen
- Filter-Funktion im Agent: Control nur empfehlen wenn Keywords im Text vorkommen
- 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
# 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
# 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
# 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
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
# 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
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 Scanbackend-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).
# 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:
[[triggers]]
type = "cron"
schedule = "0 6 * * 1" # Jeden Montag 06:00
Der Agent:
- Laedt alle gespeicherten URLs aus der DB
- Scannt jede URL
- Vergleicht mit letztem Scan-Ergebnis
- Bei Aenderungen: Email an DSB mit Diff
Oder: Einfacher Cron-Job im Backend (kein ZeroClaw noetig):
# 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-testerService 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-testerService um/website-scanEndpoint 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:
- URL eingeben:
https://www.opodo.de - Website-Scan: 6 Seiten gescannt, Google Analytics + Fonts + GTM erkannt
- SOLL/IST: 3 Dienste NICHT in DSE dokumentiert → Art. 13 Verstoss
- Consent-Test: Google Analytics laedt VOR Einwilligung → §25 TDDDG Verstoss
- Consent ablehnen: Analytics laedt TROTZ Ablehnung → KRITISCH
- Score: MEDIUM (45/100) mit 5 Findings
- Korrekturvorschlag: Einbaufertige DSE-Textbausteine per Qwen
- Email an DSB: Formatierter HTML-Report mit Handlungsanweisung
- 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