Files
breakpilot-compliance/backend-compliance/compliance/services/finding_confidence.py
T
Benjamin Admin 08671adfdf
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / detect-changes (push) Successful in 12s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / test-python-backend (push) Successful in 43s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 18s
CI / loc-budget (push) Failing after 19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
feat(audit): P82 GF-1-Pager + P87 Konfidenz-Score pro Finding
P82 — gf_one_pager.py: kompakte 5-Bullet-Kurzfassung ganz oben in der
Mail. Score (gross + Farbe), Delta-zu-Vorlauf, Top-Findings nach
HIGH/MEDIUM sortiert mit zustaendiger Rolle (DSB / Marketing / IT /
Legal / Web-Team) und Klassifizierungsbits aus dem Wizard.
Sachlicher Ton — keine 4%-Drohung, '4-8 Wochen' als realistischer
Zeitrahmen. Eingehaengt vor Critical-Findings-Block in Mail-Composition
und Replay-Pipeline.

P87 — finding_confidence.py: 13 Regex-Regeln liefern (confidence_pct,
reason) pro Finding-Label. Direkt im DOM beobachtbar = 95-98%,
Library-Mismatch = 82%, Textmuster-Match auf Pflichtangaben = 75-88%.
Im 1-Pager als kleines '(NN% Konfidenz)'-Tag mit Reason-Tooltip
hinter jedem Finding gerendert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:20:19 +02:00

87 lines
3.5 KiB
Python

"""
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' <span style="color:#94a3b8;font-size:10px" title="{reason}">'
f'({conf}% Konfidenz)</span>'
)