feat(banner): P19 + P20 — Per-Category-Click-Test + Frontend-Drilldown
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m54s
CI / test-go (push) Has been skipped
CI / detect-changes (push) Successful in 10s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 17s
CI / loc-budget (push) Successful in 17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / iace-gt-coverage (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 / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m54s
CI / test-go (push) Has been skipped
CI / detect-changes (push) Successful in 10s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 17s
CI / loc-budget (push) Successful in 17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / iace-gt-coverage (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
P19 (consent-tester): - dp-cookieconsent (TYPO3, Safetykon-Pattern) als CMP-Profil hinzu — Selektoren #dp--cookie-statistics/marketing + a.cc-allow Save-Button - Neues Signal provider_details_visible: nach Kategorie-Toggle prueft Playwright ob im Banner sichtbare Provider-/Cookie-Detail-Elemente erscheinen. Bei dp-cookieconsent (Banner ohne Listing) immer False -> HIGH-Violation "Kategorie zeigt keine Provider-/Cookie-Details — Nutzer kann nicht informiert einwilligen (Art. 7 Abs. 1 DSGVO)" - main.py serialisiert provider_details_visible + cookies_set pro Kategorie P20 (Frontend-Drilldown): - Backend: check_payloads-Tabelle um Spalte 'banner' (JSON) — voller banner_result persistiert (vorher nur in-memory). ALTER TABLE Migration idempotent. - Neuer Endpoint GET /api/compliance/agent/banner/<check_id> — liefert Quality-Score, Phases, Category-Tests, Banner-Checks, alle 46 structured_checks. - Frontend: BannerTab im /sdk/agent/audit/<id> mit Quality-Cards, 3-Phasen-Cookie-Tabelle, Per-Category-Listing (mit P19-Signal rot/gruen), Banner-Verstoesse + Rechtsgrundlagen, 46-Check-Drilldown filterbar nach Severity. - Tab-Switcher in page.tsx um "Cookie-Banner-Analyse" erweitert. - Bonus: 2 alte route.ts auf Next.js 15 Promise-params umgestellt (Build-Fix). Plus: Critical-Findings-Block nutzt provider_details_visible als primaeres Signal statt nur tracking_services-Anzahl. Smoke-Test Safetykon: 4 Critical Findings im Mail, banner-Endpoint liefert 46 checks + 3 phases + 2 categories mit provider_details_visible=False. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -841,6 +841,7 @@ async def _run_compliance_check(check_id: str, req: ComplianceCheckRequest):
|
||||
check_id=check_id,
|
||||
vendors=cmp_vendors,
|
||||
profile=extracted_profile,
|
||||
banner=banner_result,
|
||||
)
|
||||
# Unified findings (P5): bundle MC + Pflichtangaben + Vendor +
|
||||
# Redundanz in one searchable table behind /agent/findings/<id>.
|
||||
|
||||
@@ -158,13 +158,18 @@ def build_banner_deep_html(banner_result: dict | None) -> str:
|
||||
for c in non_essential:
|
||||
n = len(c.get("tracking_services") or [])
|
||||
label = c.get("category_label") or c.get("category", "?")
|
||||
if n == 0:
|
||||
color = "#dc2626"
|
||||
hint = ("Keine Anbieter sichtbar — Nutzer kann nicht "
|
||||
"informiert einwilligen (Art. 7 DSGVO)")
|
||||
pdv = c.get("provider_details_visible")
|
||||
# P19: echtes Signal aus Click-Through-Test
|
||||
if pdv is False:
|
||||
color, hint = "#dc2626", ("Banner zeigt KEINE Provider-"
|
||||
"Details — keine informierte Einwilligung")
|
||||
elif pdv is True:
|
||||
color, hint = "#16a34a", ""
|
||||
elif n == 0:
|
||||
color, hint = "#d97706", ("Keine Anbieter erkannt (vermutlich "
|
||||
"kein Provider-Listing im Banner)")
|
||||
else:
|
||||
color = "#16a34a"
|
||||
hint = ""
|
||||
color, hint = "#16a34a", ""
|
||||
parts.append(
|
||||
f'<tr style="border-top:1px solid #e2e8f0">'
|
||||
f'<td style="padding:5px 8px">{label}</td>'
|
||||
|
||||
@@ -49,23 +49,32 @@ def _detect_critical_issues(
|
||||
else _BUSSGELD_REFS.get("dark_pattern_reject"),
|
||||
})
|
||||
|
||||
# 2) Category-Tests: leere Vendor-Liste pro Kategorie (Safetykon-Pattern)
|
||||
# 2) Category-Tests: Banner zeigt keine Provider-Details pro Kategorie.
|
||||
# Bevorzugt das echte Signal aus dem Click-Through-Test (P19):
|
||||
# provider_details_visible. Fallback: leere tracking_services.
|
||||
cat_tests = br.get("category_tests") or []
|
||||
empty_cats = [c for c in cat_tests
|
||||
if not c.get("tracking_services")
|
||||
and c.get("category") != "necessary"]
|
||||
if empty_cats and len(cat_tests) >= 2:
|
||||
cats_without_details = [
|
||||
c for c in cat_tests
|
||||
if c.get("category") != "necessary"
|
||||
and (c.get("provider_details_visible") is False
|
||||
or (c.get("provider_details_visible") is None
|
||||
and not c.get("tracking_services")))
|
||||
]
|
||||
if cats_without_details and len(cat_tests) >= 2:
|
||||
cats = ", ".join(c.get("category_label", c.get("category", "?"))
|
||||
for c in empty_cats)
|
||||
for c in cats_without_details)
|
||||
issues.append({
|
||||
"key": "no_provider_per_category",
|
||||
"title": f"Cookie-Banner Kategorien ({cats}) ohne Provider-Listing",
|
||||
"title": f"Cookie-Banner: Kategorien ({cats}) zeigen keine "
|
||||
f"Provider-/Cookie-Details",
|
||||
"severity": "HIGH",
|
||||
"action": ("Pro Banner-Kategorie eine Liste der eingebundenen "
|
||||
"Anbieter + Cookie-Details (Name, Zweck, Speicherdauer, "
|
||||
"Drittlandtransfer) ergaenzen. Sonst ist die "
|
||||
"Einwilligung nicht 'informiert' nach Art. 7 DSGVO."),
|
||||
"source": "Art. 7 DSGVO, EDPB Guidelines 2/2023, DSK 2024",
|
||||
"Drittlandtransfer) sichtbar machen — am besten als "
|
||||
"ausklappbares Detail-Panel. Sonst ist die "
|
||||
"Einwilligung nicht 'informiert' nach Art. 7 DSGVO "
|
||||
"und gilt als unwirksam."),
|
||||
"source": "Art. 7 Abs. 1 DSGVO, EDPB Guidelines 2/2023, DSK 2024",
|
||||
"bussgeld": _BUSSGELD_REFS["no_provider_per_category"],
|
||||
})
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from compliance.services.unified_findings_store import (
|
||||
findings_summary,
|
||||
list_findings,
|
||||
)
|
||||
from compliance.services.compliance_audit_log import get_check_run
|
||||
from compliance.services.compliance_audit_log import get_check_run, get_check_payload
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -102,3 +102,18 @@ def get_findings(
|
||||
"count": 0,
|
||||
"findings": [],
|
||||
}
|
||||
|
||||
|
||||
@router.get("/banner/{check_id}")
|
||||
def get_banner_payload(check_id: str) -> dict:
|
||||
"""P20: full banner_result (phases, structured_checks, category_tests,
|
||||
banner_checks.violations) fuer das Voll-Audit-Frontend.
|
||||
"""
|
||||
try:
|
||||
payload = get_check_payload(check_id) or {}
|
||||
banner = payload.get("banner") or {}
|
||||
return {"found": bool(banner), "check_id": check_id, "banner": banner}
|
||||
except Exception as e:
|
||||
logger.exception("get_banner_payload failed for %s", check_id)
|
||||
return {"found": False, "check_id": check_id,
|
||||
"error": str(e)[:200], "banner": {}}
|
||||
|
||||
Reference in New Issue
Block a user