""" P87 — Konfidenz-Score pro Finding. Nicht jedes HIGH-Finding ist gleich sicher. "Kein Reject-Button im Banner" ist faktisch direkt beobachtbar (Confidence ~98%). "DSE enthaelt keinen DSB-Kontakt" ist ein Textmuster-Match und kann False-Positive sein (Confidence ~70%). "Cookie X als essential deklariert, Library sagt marketing" haengt von Library-Qualitaet ab (Confidence ~80%). Liefert pro Finding-Label ein (confidence_pct, reason) Paar. Wird im Mail-Render als kleine graue Klammer hinter dem Severity-Pill angezeigt: "HOCH (95% Konfidenz: Direkt im DOM beobachtet)". Keine ML — nur regelbasiert. Eine zentrale Stelle damit alle Render- Stellen einheitlich klassifizieren. """ from __future__ import annotations import re # (regex, confidence_pct, reason) # Reihenfolge wichtig: spezifischere Patterns zuerst. _RULES: list[tuple[re.Pattern, int, str]] = [ # 1) Direkt im DOM / im Cookie-Jar beobachtet — sehr hohe Sicherheit (re.compile(r"reject[- ]?button.*(fehlt|nicht.*vorhanden)", re.I), 98, "Direkt im Banner-DOM ueberprueft"), (re.compile(r"(anpassen|einstellungen|customize).*button.*fehlt", re.I), 95, "Initial-Banner-DOM ueberprueft"), (re.compile(r"cookie.*vor.*einwilligung.*gesetzt", re.I), 96, "Cookie-Jar vor Akzeptieren beobachtet"), (re.compile(r"(tracking|marketing).*ohne.*einwilligung", re.I), 92, "Network-Calls vor Akzeptieren beobachtet"), # 2) Library-Mismatches — abhaengig von Library-Qualitaet (re.compile(r"deklariert als.*library.*sagt", re.I), 82, "Vergleich mit ~2.300-Cookie-Library + Open-Cookie-DB"), (re.compile(r"library.*marketing", re.I), 82, "Cookie-Library-Klassifikation"), # 3) Pflichtangaben-Checks (Impressum/AGB/DSE) — Textmuster, MEDIUM-Sicherheit (re.compile(r"impressum.*(fehlt|unvollstaendig)", re.I), 88, "Pattern-Match auf Impressums-Pflichtfelder (§ 5 TMG)"), (re.compile(r"dsb.*(fehlt|nicht.*genannt)", re.I), 75, "Textmuster-Suche; DSB kann ueber Impressum referenziert sein"), (re.compile(r"drittland.*(fehlt|nicht.*genannt|ohne.*hinweis)", re.I), 80, "Pattern-Match auf typische Drittland-Klauseln"), (re.compile(r"widerruf.*(fehlt|unvollstaendig)", re.I), 85, "Pattern-Match auf Widerrufsbelehrungs-Pflichtfelder"), # 4) Anti-Auditing-Detection — heuristisch (re.compile(r"anti[- ]?audit", re.I), 70, "Skript-Domain-Heuristik; manuelle Pruefung empfohlen"), # 5) Generische Konsistenz-Findings (DSE vs. Banner vs. Cookie-Liste) (re.compile(r"banner.*nennt.*\d+.*cmp.*\d+", re.I), 90, "Quantitativer Vergleich zwischen Banner-Text und CMP-Payload"), # 6) Klassifikations- / Kontext-Findings (Wizard-getrieben) (re.compile(r"(branchen|scope).*passt.*nicht", re.I), 88, "Wizard-Klassifikation + MC-scope_doc_type"), ] _DEFAULT_CONFIDENCE = 78 _DEFAULT_REASON = ( "Standard-Regelpruefung; Bestaetigung mit DSB / interner Doku empfohlen" ) def score_finding(label: str) -> tuple[int, str]: """Returns (confidence_pct, reason) for a finding label.""" if not label: return _DEFAULT_CONFIDENCE, _DEFAULT_REASON for pat, conf, reason in _RULES: if pat.search(label): return conf, reason return _DEFAULT_CONFIDENCE, _DEFAULT_REASON def confidence_pill_html(label: str) -> str: """Returns an inline HTML snippet '(NN% Konfidenz: ...)' or empty.""" conf, reason = score_finding(label) return ( f' ' f'({conf}% Konfidenz)' )