feat(audit-mail): P58/P59c/P60b/P61/P62 — Mercedes-Cycle Phase 1 abgeschlossen
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 38s
CI / test-python-document-crawler (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 / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 14s
CI / loc-budget (push) Failing after 15s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped

P58  Anti-Audit-Detection robuster (script-domain + settings-spezifisch —
     war bereits im Code, jetzt sauber als completed dokumentiert).

P59c DACH-Custom-Cookies in compliance.cookie_library: Borlabs,
     etracker, Matomo/Piwik, Userlike, Cookiebot/Cookieyes/Usercentrics,
     Akamai/Cloudflare/Datadome Bot-Manager + HubSpot. 21 neue Eintraege
     (3 von 24 schon via Open-Cookie-Database vorhanden).
     Script: backend-compliance/scripts/seed_dach_cookies.py.

P60b Vendor-Pattern-Dedupe mit Fuzzy-Match (Jaccard >= 0.7) statt exakter
     Tuple-Equality. Vendors mit teilweise befuellten Feldern (z.B.
     Sitzland eingetragen) fallen nicht mehr aus der globalen Notice —
     Bug: Amazon/Psyma/Qualtrics hatten zuvor wiederholte per-row Actions.

P61  "Untergeschobene Cookies"-Erkennung — wenn ein deklarierter Vendor
     (z.B. Google Tag Manager) automatisch weitere mitbringt (GA + GCL_AU
     + DoubleClick), werden diese als separater Mail-Block (gelb) mit
     COOKIE/VENDOR-Badges + Quellen-Doku ausgewiesen. Neuer Service:
     compliance.services.vendor_package_cookies (8 Primary-Vendors mit
     je 2-4 implicit Cookies/Vendors).

P62  Marketing-Manager-Disclaimer "Was wir sehen / nicht sehen" als
     blauer Box-Block direkt unter dem Critical-Findings-Block. Erklaert
     Grenzen unseres Audits (Server-Side-Tracking, Vendor-interne
     Datenweitergabe, Cross-Page-Banner) und Risiko des Falschvertrauens
     in einen 100%-Score. Neuer Renderer: compliance.api.scope_disclaimer.

Architektur: VVT-Tabellen-Renderer aus agent_doc_check_extras.py (552
LOC -> 242 LOC) in compliance.api.vvt_table_renderer ausgelagert, um den
500-LOC-Hardcap einzuhalten.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-21 08:01:27 +02:00
parent 57c0f940a2
commit 603381a67f
7 changed files with 858 additions and 300 deletions
@@ -231,5 +231,55 @@ def build_banner_deep_html(banner_result: dict | None) -> str:
)
parts.append('</ul></div>')
# 6) P61: Untergeschobene Cookies/Vendors (Vendor-Package)
impl_findings = banner_result.get("implicit_vendor_findings") or []
if impl_findings:
# Gruppiert nach primary_vendor: pro Primary die mitgelaufenen Items
by_primary: dict[str, list[dict]] = {}
for f in impl_findings:
by_primary.setdefault(f["primary_vendor"], []).append(f["implicit"])
parts.append(
'<div style="margin:14px 0 4px;padding:8px 12px;'
'background:#fef3c7;border-left:3px solid #d97706;border-radius:4px">'
'<div style="font-size:12px;color:#92400e;font-weight:600;'
'margin-bottom:6px">Untergeschobene Cookies / Vendors '
'(P61 — mit Hauptanbieter automatisch mitgeladen)</div>'
'<div style="font-size:10px;color:#92400e;margin-bottom:8px">'
'Diese Cookies/Vendors kommen automatisch mit dem deklarierten '
'Hauptanbieter mit — Marketing-Manager waehlen sie oft nicht '
'bewusst aus, sie sind aber zustimmungspflichtig.</div>'
)
for primary, impls in by_primary.items():
parts.append(
f'<div style="font-size:11px;color:#1e293b;margin:6px 0">'
f'<strong>{primary}</strong> bringt automatisch:</div>'
'<ul style="margin:0 0 8px 18px;padding:0;font-size:11px;color:#1e293b">'
)
for impl in impls:
tag = ('<span style="font-size:9px;background:#dc2626;color:#fff;'
'padding:1px 5px;border-radius:3px;margin-right:6px">'
'COOKIE</span>' if impl["type"] == "cookie" else
'<span style="font-size:9px;background:#7c3aed;color:#fff;'
'padding:1px 5px;border-radius:3px;margin-right:6px">'
'VENDOR</span>')
cat_color = {"marketing": "#dc2626", "statistics": "#d97706",
"functional": "#0891b2", "essential": "#16a34a"}.get(
impl.get("category", ""), "#475569")
parts.append(
f'<li style="margin-bottom:5px">{tag}'
f'<code style="font-size:10px;background:#f1f5f9;'
f'padding:1px 4px;border-radius:2px">{impl["name"]}</code> '
f'<span style="font-size:9px;color:{cat_color};'
f'margin-left:4px">[{impl.get("category","?")}]</span>'
f'<div style="font-size:10px;color:#475569;margin-top:2px">'
f'{impl.get("why","")[:240]}</div>'
f'<div style="font-size:9px;color:#94a3b8;font-style:italic">'
f'Quelle: <a href="{impl.get("source_url","")}" '
f'style="color:#94a3b8">{impl.get("source_url","")[:80]}</a>'
f'</div></li>'
)
parts.append('</ul>')
parts.append('</div>')
parts.append('</div>')
return "".join(parts)