Files
breakpilot-compliance/zeroclaw/PLAN-control-relevance-filter.md
Benjamin Admin 24fb1e14e0 docs: add Phase 4b — SOLL/IST Dienstleister-Abgleich (DSE vs. Website)
Automated comparison: services mentioned in privacy policy vs. actually
embedded on website. Three categories: undocumented (Art. 13 violation),
outdated (cleanup), correctly documented (check third country only).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 15:20:12 +02:00

21 KiB

Plan: Control Relevance Filter — Generische Controls kontextsensitiv filtern

Problem

Die UCCA-Engine empfiehlt Controls pauschal basierend auf Intake-Flags (Boolean-Felder wie personal_data: true, marketing: true). Sie prueft NICHT, ob der analysierte Text die Bedingungen fuer einen spezifischen Control tatsaechlich erfuellt.

Konkretes Beispiel (Opodo-Test, 2026-04-28)

  • Control: [C_TRANSPARENCY] Nutzer informieren dass sie mit KI interagieren
  • Quelle: AI Act Art. 52 — nur relevant wenn KI eingesetzt wird
  • Opodo sagt: "automated processing" (kann regelbasierte Software sein, muss keine KI sein)
  • Ergebnis: False Positive — Control wird empfohlen obwohl kein KI-Einsatz belegt ist

Skalierung

Von ~166.740 Controls in der RAG-Datenbank wird ein unbekannter Prozentsatz bei jeder Bewertung generisch empfohlen. Jedes False Positive untergräbt das Vertrauen des Nutzers und macht das Tool fuer Abmahnungen unbrauchbar.

Loesung: 3-Stufen Relevance Filter

Stufe 1: Regelbasierter Vorfilter (deterministisch, schnell)

Jeder Control bekommt ein relevance_conditions Feld (JSON):

{
  "control_id": "C_TRANSPARENCY",
  "relevance_conditions": {
    "text_must_contain_any": ["KI", "kuenstliche Intelligenz", "artificial intelligence",
                               "machine learning", "maschinelles Lernen", "neural", "deep learning",
                               "AI system", "AI-System", "algorith"],
    "text_must_not_contain": [],
    "requires_intake_flag": "automation",
    "min_confidence": 0.5
  }
}

Implementierung:

  • Neues Feld relevance_conditions in compliance.canonical_controls (JSONB)
  • Funktion check_relevance(control, source_text) -> (relevant: bool, confidence: float)
  • Laeuft NACH dem UCCA-Assessment, BEVOR das Ergebnis zurueckgegeben wird
  • Filtert Controls raus deren Keywords im Quelltext nicht vorkommen

Aufwand: ~200 LOC Python, kein LLM-Call noetig Datei: ai-compliance-sdk/internal/ucca/relevance_filter.go oder backend-compliance/compliance/services/relevance_filter.py

Stufe 2: LLM-Validierung (fuer High-Value Controls)

Fuer Controls mit severity >= HIGH oder wenn der regelbasierte Filter unsicher ist (confidence < 0.7), wird Qwen gefragt:

Gegeben dieser Dokumenttext:
"[...Auszug...]"

Ist der folgende Control relevant fuer dieses Dokument?
Control: "[C_TRANSPARENCY] Nutzer informieren dass sie mit KI interagieren"
Rechtsgrundlage: Art. 52 AI Act

Antworte NUR mit: JA (mit Begruendung) oder NEIN (mit Begruendung)

Implementierung:

  • Neuer Endpoint: POST /sdk/v1/ucca/validate-controls
  • Nimmt: assessment_id, source_text, controls[]
  • Gibt zurueck: controls[] mit relevant: bool, reason: string
  • Cached: Gleicher Text + Control = gleiche Antwort (24h TTL)

Aufwand: ~150 LOC, 1 LLM-Call pro Control (parallelisierbar)

Stufe 3: Follow-Up-Fragen an den Nutzer (Hybrid)

Wenn weder Regel noch LLM sicher entscheiden koennen:

Follow-Up: "Setzt der Anbieter KI oder maschinelles Lernen ein?"
→ Ja: Control bleibt
→ Nein: Control wird entfernt
→ Unsicher: Control bleibt mit Hinweis "Nicht verifizierbar"

Bereits implementiert: Das follow_up_questions System im Agent-Endpoint.

Datenmodell-Aenderung

-- Neues Feld in canonical_controls
ALTER TABLE compliance.canonical_controls
ADD COLUMN IF NOT EXISTS relevance_conditions JSONB DEFAULT '{}';

-- Index fuer schnelle Abfrage
CREATE INDEX IF NOT EXISTS idx_controls_relevance
ON compliance.canonical_controls USING gin (relevance_conditions);

Architektur

UCCA Assessment
    │
    ▼
┌────────────────────┐
│ Stufe 1: Regelfilter│ ← text_must_contain_any, intake_flags
│ (deterministisch)   │
└────────┬───────────┘
         │ unsicher oder high-severity
         ▼
┌────────────────────┐
│ Stufe 2: LLM-Check │ ← Qwen validiert Relevanz
│ (1 Call/Control)    │
└────────┬───────────┘
         │ immer noch unsicher
         ▼
┌────────────────────┐
│ Stufe 3: Follow-Up │ ← Nutzer beantwortet Frage
│ (Frontend)          │
└────────────────────┘

Implementierungsreihenfolge

Phase 1: Regelfilter (1 Tag)

  1. Migration: relevance_conditions Feld zu canonical_controls
  2. Seed-Script: Top-20 generische Controls mit Bedingungen versehen (C_TRANSPARENCY, C_EXPLICIT_CONSENT, C_DSFA_REQUIRED, etc.)
  3. Filter-Funktion in agent_analyze_routes.py
  4. Test: Opodo erneut analysieren — C_TRANSPARENCY sollte rausfallen

Phase 2: LLM-Validierung (1 Tag)

  1. Neuer SDK-Endpoint /sdk/v1/ucca/validate-controls
  2. Integration in den Agent-Workflow
  3. Caching-Layer (Redis/Valkey)

Phase 3: Batch-Seeding (2-3 Tage)

  1. Pipeline-Job: Fuer alle 166k Controls relevance_conditions generieren (LLM-gestuetzt: "Welche Keywords im Quelltext wuerden diesen Control relevant machen?")
  2. Qualitaetspruefung: Stichprobe von 100 Controls manuell validieren

Betroffene Dateien

Datei Aenderung
backend-compliance/compliance/api/agent_analyze_routes.py Filter-Integration
backend-compliance/compliance/services/relevance_filter.py NEU: Regelfilter
ai-compliance-sdk/internal/ucca/relevance_filter.go NEU: SDK-seitig (alternativ)
ai-compliance-sdk/internal/api/handlers/ucca_handlers.go Neuer Endpoint
Migration relevance_conditions Spalte
control-pipeline/ Batch-Seeding Job (Phase 3)

Phase 4: Website-Scan (Multi-Page Crawl)

Problem

Aktuell analysieren wir nur EINE URL (z.B. /datenschutz/). Aber relevante Hinweise auf KI, Chatbots, automatisierte Entscheidungen oder Tracking koennen auf ANDEREN Seiten der Website stehen:

  • Chatbot-Widget auf der Startseite (nicht auf der Datenschutzseite)
  • "Powered by ChatGPT" im Footer
  • KI-gestuetzte Produktempfehlungen auf der Shopseite
  • Cookie-Scripts die Tracking-Dienste laden (Google Analytics, Meta Pixel, etc.)
  • Chatbot-Anbieter wie Intercom, Drift, Zendesk, Tidio im HTML

Loesung: Lightweight Website-Scan

Kein vollstaendiger Crawl (zu langsam, zu invasiv), sondern ein gezielter Scan von 5-10 strategischen Seiten:

Eingabe: https://www.opodo.de/datenschutz/

Automatisch gescannte Seiten:
1. Startseite:           https://www.opodo.de/
2. Datenschutz (bereits): https://www.opodo.de/datenschutz/
3. Impressum:            https://www.opodo.de/impressum/ (aus Footer-Links)
4. AGB:                  https://www.opodo.de/agb/ (aus Footer-Links)
5. Cookie-Policy:        https://www.opodo.de/cookies/ (falls vorhanden)

Scan-Logik

Schritt 1: Startseite holen + Footer-Links extrahieren

# Aus der Startseite die typischen Footer-Links extrahieren:
footer_patterns = [
    r'href="([^"]*(?:impressum|imprint|legal)[^"]*)"',
    r'href="([^"]*(?:datenschutz|privacy|dsgvo)[^"]*)"',
    r'href="([^"]*(?:agb|terms|nutzungsbedingungen)[^"]*)"',
    r'href="([^"]*(?:cookie|cookies)[^"]*)"',
    r'href="([^"]*(?:kontakt|contact)[^"]*)"',
]

Schritt 2: Jede Seite auf KI/Chatbot/Tracking-Indikatoren scannen

AI_INDICATORS = {
    # Chatbot-Widgets (JavaScript-Einbindungen)
    "chatbot_widgets": [
        r"intercom",          # Intercom (KI-gestuetzt)
        r"drift\.com",        # Drift Chatbot
        r"tidio",             # Tidio Chat
        r"zendesk",           # Zendesk Chat
        r"crisp\.chat",       # Crisp Chat
        r"livechat",          # LiveChat
        r"hubspot.*chat",     # HubSpot Chat
        r"tawk\.to",          # Tawk.to
        r"freshchat",         # Freshworks
        r"dialogflow",        # Google Dialogflow
        r"watson.*assistant", # IBM Watson
        r"chatgpt|openai",    # OpenAI/ChatGPT
        r"anthropic|claude",  # Anthropic/Claude
    ],
    # KI-Hinweise im Text
    "ai_text_mentions": [
        r"k(?:ue|ü)nstliche.?intelligenz",
        r"artificial.?intelligence",
        r"machine.?learning",
        r"maschinelles.?lernen",
        r"KI.?gest(?:ue|ü)tzt",
        r"AI.?powered",
        r"algorithm",
        r"automatisierte.?entscheidung",
        r"automated.?decision",
        r"profiling",
        r"personalisier",    # Personalisierung
    ],
    # Tracking & Analytics (EU + non-EU)
    "tracking_analytics": [
        # Google (USA)
        r"google.?analytics|gtag|UA-\d+|G-\w+",
        r"googletagmanager|gtm\.js",
        r"google.?ads|googleads|adwords",
        r"doubleclick\.net",
        # Meta (USA)
        r"facebook.?pixel|fbq\(|connect\.facebook",
        r"meta.?pixel",
        # Microsoft (USA)
        r"clarity\.ms",     # Microsoft Clarity
        r"bing\.com/bat",   # Bing Ads
        r"linkedin\.com/insight", # LinkedIn Insight
        # Analytics-Anbieter
        r"hotjar",          # Hotjar (Malta/EU — OK)
        r"segment\.com",    # Segment (USA)
        r"mixpanel",        # Mixpanel (USA)
        r"amplitude",       # Amplitude (USA)
        r"heap\.io",        # Heap (USA)
        r"posthog",         # PostHog (USA, self-host moeglich)
        r"matomo|piwik",    # Matomo (EU — self-host = OK, Cloud = pruefen)
        r"plausible",       # Plausible (EU — OK)
        r"fathom",          # Fathom (Kanada — Angemessenheitsbeschluss)
        r"pirsch",          # Pirsch (DE — OK)
        r"umami",           # Umami (self-host)
    ],
    # CDN und Drittanbieter-Dienste (Drittlandtransfer-Risiko)
    "third_party_services": [
        # CDN (pruefen ob Drittland)
        r"cdn\.cloudflare\.com",      # Cloudflare (USA)
        r"fastly\.net",               # Fastly (USA)
        r"akamai",                    # Akamai (USA)
        r"cdn\.jsdelivr\.net",        # jsDelivr (international)
        r"unpkg\.com",                # unpkg (USA)
        r"cdnjs\.cloudflare\.com",    # cdnjs (USA)
        r"stackpath",                 # StackPath (USA)
        r"bunny\.net|bunnycdn",       # BunnyCDN (Slowenien/EU — OK)
        r"keycdn",                    # KeyCDN (Schweiz — Angemessenheit)
        # Fonts (IP-Uebermittlung!)
        r"fonts\.googleapis\.com",    # Google Fonts (USA — DSGVO-Verstoss!)
        r"fonts\.gstatic\.com",       # Google Fonts CDN
        r"use\.typekit\.net",         # Adobe Fonts (USA)
        # Captcha
        r"recaptcha|grecaptcha",      # Google reCAPTCHA (USA)
        r"hcaptcha",                  # hCaptcha (USA)
        r"turnstile.*cloudflare",     # Cloudflare Turnstile (USA)
        # Maps
        r"maps\.googleapis\.com",     # Google Maps (USA)
        r"maps\.google\.com",
        r"openstreetmap",             # OpenStreetMap (EU — OK)
        r"mapbox",                    # Mapbox (USA)
        # Video
        r"youtube\.com|youtube-nocookie", # YouTube (USA)
        r"vimeo\.com",                # Vimeo (USA)
        r"wistia",                    # Wistia (USA)
        # Social Media Embeds
        r"platform\.twitter\.com|x\.com/embed", # X/Twitter (USA)
        r"instagram\.com/embed",      # Instagram (USA)
        r"linkedin\.com/embed",       # LinkedIn (USA)
        # Content Moderation
        r"besedo",                    # Besedo (Schweden/EU — OK, aber pruefen)
        # Payment (PCI-DSS relevant)
        r"stripe\.com|js\.stripe",    # Stripe (USA)
        r"paypal\.com",               # PayPal (USA)
        r"adyen",                     # Adyen (NL/EU — OK)
        r"mollie",                    # Mollie (NL/EU — OK)
        # Andere
        r"sentry\.io|sentry-cdn",     # Sentry Error Tracking (USA)
        r"intercom\.io",              # Intercom (USA) — auch in chatbot_widgets
        r"zendesk\.com",              # Zendesk (USA)
        r"freshdesk|freshworks",      # Freshworks (USA/Indien)
    ],
}

Drittland-Erkennung

Fuer jeden erkannten externen Dienst wird geprueft ob er aus einem Drittland stammt (kein EU/EWR-Staat, kein Angemessenheitsbeschluss). Dafuer wird eine Registry gepflegt:

# Statische Registry — ca. 80 Eintraege
THIRD_PARTY_REGISTRY = {
    "google_analytics":  {"provider": "Google LLC", "country": "US", "eu_adequate": False, "requires_consent": True, "legal_ref": "Art. 44-49 DSGVO, Schrems II"},
    "google_fonts":      {"provider": "Google LLC", "country": "US", "eu_adequate": False, "requires_consent": True, "legal_ref": "LG Muenchen I, Az. 3 O 17493/20 (Google Fonts Urteil)"},
    "facebook_pixel":    {"provider": "Meta Platforms", "country": "US", "eu_adequate": False, "requires_consent": True, "legal_ref": "Art. 44-49 DSGVO"},
    "cloudflare_cdn":    {"provider": "Cloudflare Inc", "country": "US", "eu_adequate": False, "requires_consent": False, "legal_ref": "Art. 44-49 DSGVO, berechtigtes Interesse moeglich"},
    "matomo_cloud":      {"provider": "Matomo (InnoCraft)", "country": "NZ", "eu_adequate": True, "requires_consent": True, "legal_ref": "Neuseeland hat Angemessenheitsbeschluss"},
    "matomo_selfhost":   {"provider": "Self-hosted", "country": "depends", "eu_adequate": True, "requires_consent": False, "legal_ref": "Kein Drittlandtransfer bei Self-Hosting"},
    "plausible":         {"provider": "Plausible Insights", "country": "EE", "eu_adequate": True, "requires_consent": False, "legal_ref": "EU-Anbieter, cookieless"},
    "bunnycdn":          {"provider": "BunnyCDN d.o.o.", "country": "SI", "eu_adequate": True, "requires_consent": False, "legal_ref": "EU-Anbieter"},
    "stripe":            {"provider": "Stripe Inc", "country": "US", "eu_adequate": False, "requires_consent": False, "legal_ref": "Art. 6(1)(b) Vertragserfuellung, SCCs"},
    "besedo":            {"provider": "Besedo AB", "country": "SE", "eu_adequate": True, "requires_consent": False, "legal_ref": "EU-Anbieter"},
    # ... ~80 weitere Eintraege
}

Generierte Findings

Beispiel: Opodo mit erweitertem Scan:

Externe Dienste erkannt:
  - Google Analytics (G-03F834EHLM) — USA, kein Angemessenheitsbeschluss
    → FINDING: Drittlandtransfer USA ohne Einwilligung (Art. 44 DSGVO)
  - Google Fonts (fonts.googleapis.com) — USA
    → FINDING: Google Fonts Einbindung (LG Muenchen I, Az. 3 O 17493/20)
  - Didomi CMP — Frankreich (EU — OK)
  - Bootstrap CDN (jsdelivr.net) — International, pruefen
    → FOLLOW-UP: "Wird das CDN aus der EU oder einem Drittland geladen?"

Phase 4b: Soll-Ist-Abgleich (Dienstleister DSE vs. Website)

Der wertvollste Output des Agents: automatischer Abgleich zwischen dem was in der Datenschutzerklaerung STEHT und dem was tatsaechlich auf der Website EINGEBUNDEN ist.

Schritt 1: IST — Website scannen (bereits in Phase 4) Alle eingebundenen externen Dienste per HTML/Script-Analyse erkennen.

Schritt 2: SOLL — Datenschutzerklaerung parsen Aus dem DSE-Text extrahieren welche Dienstleister erwaehnt werden:

# Qwen/LLM extrahiert strukturiert:
PROMPT = """
Extrahiere aus dieser Datenschutzerklaerung ALLE erwaehnten Dienstleister/Tools.
Fuer jeden Dienstleister nenne:
- Name (z.B. "Google Analytics")
- Zweck (z.B. "Webanalyse")
- Land/Sitz (z.B. "USA")
- Genannte Rechtsgrundlage (z.B. "Einwilligung" oder "berechtigtes Interesse")
- Genannte Schutzmassnahme (z.B. "Standardvertragsklauseln")

Antworte als JSON-Array.
"""

Schritt 3: Abgleich → 3 Kategorien

Kategorie Bedeutung Finding-Typ
Eingebunden + NICHT in DSE Informationspflicht verletzt HIGH — Art. 13 DSGVO Verstoss
In DSE + NICHT eingebunden Veraltete/irrefuehrende DSE LOW — Aufraumbedarf
Eingebunden + in DSE Korrekt dokumentiert OK — nur Drittland pruefen

Beispiel-Output fuer Opodo:

Dienstleister-Abgleich (opodo.de)
══════════════════════════════════

Eingebunden auf Website         In DSE erwaehnt?    Status
─────────────────────────────── ─────────────────── ───────
Google Analytics (G-03F834EHLM) Ja (Abschnitt 3.6)  ✓ OK — aber USA, SCCs pruefen
Didomi CMP                      Ja (Cookie Notice)   ✓ OK — Frankreich/EU
Bootstrap CDN (jsdelivr)        Nein                  ✗ FINDING: Nicht in DSE
Google Tag Manager              Ja (Abschnitt 3.6)   ✓ OK

In DSE erwaehnt                 Auf Website gefunden? Status
─────────────────────────────── ───────────────────── ───────
Amadeus IT (Buchungssystem)     Nicht pruefbar        ? Backend-Dienst
Adyen (Zahlungsabwicklung)      Nicht pruefbar        ? Backend-Dienst
Salesforce (CRM)                Nicht pruefbar        ? Backend-Dienst

Zusammenfassung:
- 1 Dienstleister eingebunden aber NICHT in DSE dokumentiert (jsdelivr CDN)
- 3 Backend-Dienste in DSE erwaehnt, nicht im Frontend pruefbar
- Empfehlung: jsdelivr CDN in Datenschutzerklaerung aufnehmen oder lokal hosten

Dieser Output allein ist fuer einen Datenschutzbeauftragten Gold wert — er spart Stunden manueller Arbeit und deckt Luecken auf die bei Website-Updates entstehen.

Controls die durch Drittland-Dienste ausgeloest werden

Erkannter Dienst Control
Jeder US-Dienst ohne SCCs C_THIRD_COUNTRY_TRANSFER: Drittlandtransfer absichern (Art. 44-49 DSGVO)
Google Fonts remote C_GOOGLE_FONTS: Fonts lokal einbinden (LG Muenchen I Urteil)
Tracking ohne Consent-Banner C_EXPLICIT_CONSENT: Einwilligung vor Tracking einholen
reCAPTCHA C_CAPTCHA_PRIVACY: Datenschutzkonformen Captcha-Dienst nutzen
YouTube Embed C_VIDEO_EMBED: 2-Klick-Loesung oder youtube-nocookie verwenden

**Schritt 3: Ergebnis aggregieren**
```python
scan_result = {
    "pages_scanned": 5,
    "chatbot_detected": True,      # z.B. Intercom auf Startseite
    "chatbot_provider": "intercom", # Identifizierter Anbieter
    "ai_mentions_found": False,     # Kein expliziter KI-Text
    "tracking_services": ["google_analytics", "facebook_pixel"],
    "tracking_count": 2,
}

Schritt 4: Scan-Ergebnis in Relevanzpruefung einbeziehen

  • Chatbot erkannt → C_TRANSPARENCY wird relevant (auch ohne KI-Text)
  • Tracking erkannt → C_EXPLICIT_CONSENT wird relevant
  • Kein KI-Nachweis auf gesamter Website → C_TRANSPARENCY faellt weg

Implementierung

Neue Datei: backend-compliance/compliance/services/website_scanner.py (~200 LOC)

class WebsiteScanner:
    async def scan(self, base_url: str) -> ScanResult:
        """Scan 5-10 pages for AI, chatbot, and tracking indicators."""
        pages = await self._discover_pages(base_url)
        indicators = {}
        for page_url in pages[:10]:
            html = await self._fetch(page_url)
            indicators[page_url] = self._detect_indicators(html)
        return self._aggregate(indicators)

Integration in Agent-Workflow:

  • Zwischen Schritt 1 (Fetch) und Schritt 3 (UCCA Assess)
  • Scan-Ergebnis fliesst in die Intake-Flags UND in den Relevanzfilter
  • Scan-Ergebnis wird im Response zurueckgegeben (Transparenz)

Frontend-Erweiterung:

  • "Erweiterte Analyse" Toggle: Nur Einzelseite vs. Website-Scan
  • Scan-Ergebnis als aufklappbare Sektion: "5 Seiten gescannt, Chatbot auf Startseite erkannt"

Aufwand

Komponente LOC Zeit
website_scanner.py ~200 0.5 Tage
Integration in agent_analyze_routes.py ~50 2h
Frontend: Scan-Ergebnis anzeigen ~80 2h
Tests ~100 2h

Beispiel: Opodo mit Website-Scan

Seiten gescannt: 5
  - https://www.opodo.de/ → Didomi Cookie-Consent, Google Analytics
  - https://www.opodo.de/datenschutz/ → Datenschutzerklaerung
  - https://www.opodo.de/impressum/ → 404 (FINDING!)
  - https://www.opodo.de/agb/ → AGB vorhanden
  - https://www.opodo.de/cookies/ → Cookie-Policy

Chatbot erkannt: Nein
KI-Hinweise: Nein
Tracking: Google Analytics (G-03F834EHLM), Didomi CMP

→ C_TRANSPARENCY: NICHT relevant (kein KI-Nachweis auf gesamter Website)
→ C_EXPLICIT_CONSENT: Relevant (Google Analytics + Didomi = Tracking aktiv)
→ Impressum-Finding: 404 auf /impressum/ (§5 TMG Verstoss)

Risiken

Risiko Mitigation
Zu aggressive Filterung (False Negatives) Stufe 1 nur fuer klare Faelle, Stufe 2 als Fallback
LLM-Kosten bei vielen Controls Caching + nur high-severity Controls
Datenbank-Migration auf Production ADD COLUMN IF NOT EXISTS ist non-blocking
166k Controls ohne relevance_conditions Default {} = kein Filter = bisheriges Verhalten

Testfaelle

  1. Opodo-Test: C_TRANSPARENCY sollte NICHT mehr empfohlen werden (kein KI-Nachweis)
  2. Chatbot-Anbieter: C_TRANSPARENCY SOLL empfohlen werden (KI explizit erwaehnt)
  3. Arztpraxis-Website: C_DSFA_REQUIRED SOLL empfohlen werden (Gesundheitsdaten)
  4. Blog ohne Tracking: Nur minimale Controls (Impressum, Datenschutzerklaerung)