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:
@@ -99,8 +99,69 @@ CMP_CATEGORY_CONFIG: dict[str, dict] = {
|
||||
"marketing": "[data-purpose='advertising_purposes'] input, [data-purpose='ads'] input",
|
||||
},
|
||||
},
|
||||
# P19: TYPO3 dp-cookieconsent (Dirk Persky) — basiert auf osano cookieconsent.
|
||||
# Banner zeigt Checkboxes direkt; KEIN Settings-Modal, KEINE Provider-Details.
|
||||
# Detection: Checkbox-IDs dp--cookie-*. Provider-/Cookie-Liste fehlt
|
||||
# systematisch -> explizites Finding.
|
||||
"dp-cookieconsent": {
|
||||
"settings_button": None,
|
||||
"save_button": "a.cc-allow:not(.cc-allow-all), button:has-text('Speichern')",
|
||||
"categories": {
|
||||
"statistics": "#dp--cookie-statistics",
|
||||
"marketing": "#dp--cookie-marketing",
|
||||
},
|
||||
},
|
||||
"Cookie Consent (Insites)": { # alias — banner_detector benennt dp-cookieconsent so
|
||||
"settings_button": None,
|
||||
"save_button": "a.cc-allow:not(.cc-allow-all), button:has-text('Speichern')",
|
||||
"categories": {
|
||||
"statistics": "#dp--cookie-statistics, input[id*='statistic' i]",
|
||||
"marketing": "#dp--cookie-marketing, input[id*='marketing' i]",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Selektoren um zu prueffen ob ein Banner Provider-/Cookie-Details
|
||||
# nach Kategorie-Selektion ZEIGT (Per-Category-Vendor-Listing).
|
||||
_PROVIDER_DETAIL_SELECTORS = (
|
||||
"[class*='cookie-list' i]",
|
||||
"[class*='cookielist' i]",
|
||||
"[class*='vendor-list' i]",
|
||||
"[class*='vendor_list' i]",
|
||||
"[class*='provider-list' i]",
|
||||
"[class*='cookie-detail' i]",
|
||||
"[class*='vendor-detail' i]",
|
||||
"[class*='cookie-item' i]",
|
||||
"[class*='vendor-item' i]",
|
||||
"table[class*='cookie' i]",
|
||||
"table[class*='vendor' i]",
|
||||
"ul[class*='cookie' i] li",
|
||||
)
|
||||
|
||||
|
||||
async def _provider_details_visible(page, category_label: str) -> bool:
|
||||
"""True wenn im Banner sichtbare Provider-/Cookie-Details existieren.
|
||||
|
||||
Heuristik: irgendein Element matched die Detail-Selektoren UND ist visible.
|
||||
Bei Banner wie dp-cookieconsent (kein Listing) immer False -> Finding.
|
||||
"""
|
||||
try:
|
||||
return await page.evaluate(
|
||||
"""(selectors) => {
|
||||
for (const sel of selectors) {
|
||||
const els = document.querySelectorAll(sel);
|
||||
for (const el of els) {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.width > 30 && r.height > 10) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}""",
|
||||
list(_PROVIDER_DETAIL_SELECTORS),
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# Generic category keywords for fallback detection
|
||||
CATEGORY_KEYWORDS = {
|
||||
"statistics": ["statistik", "analytics", "analyse", "statistics", "messung", "reichweite"],
|
||||
@@ -125,6 +186,8 @@ class CategoryTestResult:
|
||||
cookies_set: list[str] = field(default_factory=list)
|
||||
tracking_services: list[str] = field(default_factory=list)
|
||||
violations: list[dict] = field(default_factory=list)
|
||||
# P19: Per-Category-Transparenz im Banner
|
||||
provider_details_visible: bool = False
|
||||
|
||||
|
||||
async def detect_categories(page: Page, banner: BannerInfo) -> list[CategoryInfo]:
|
||||
@@ -242,6 +305,27 @@ async def test_single_category(
|
||||
result.cookies_set = [c.get("name", "") for c in await context.cookies()]
|
||||
result.tracking_services = find_tracking_services(result.scripts_loaded)
|
||||
|
||||
# P19: pruefe ob das Banner Provider-/Cookie-Details fuer diese
|
||||
# Kategorie sichtbar macht — bei dp-cookieconsent (Safetykon) immer
|
||||
# False -> kritischer Verstoss (Art. 7 DSGVO: keine informierte
|
||||
# Einwilligung ohne Detail-Listing pro Kategorie).
|
||||
result.provider_details_visible = await _provider_details_visible(
|
||||
page, category.label,
|
||||
)
|
||||
if not result.provider_details_visible:
|
||||
result.violations.append({
|
||||
"service": "Cookie-Banner",
|
||||
"severity": "HIGH",
|
||||
"text": (f"Kategorie '{category.label}' zeigt keine "
|
||||
f"Provider-/Cookie-Details im Banner — Nutzer "
|
||||
f"kann nicht informiert einwilligen "
|
||||
f"(Art. 7 Abs. 1 DSGVO)."),
|
||||
"legal_ref": "Art. 7 Abs. 1 DSGVO, EDPB Guidelines 2/2023, "
|
||||
"DSK-OH Telemedien 2024",
|
||||
"expected_category": category.name,
|
||||
"actual_category": category.name,
|
||||
})
|
||||
|
||||
# Find violations: services that don't belong to this category
|
||||
for service in result.tracking_services:
|
||||
expected_cat = SERVICE_CATEGORY_MAP.get(service)
|
||||
|
||||
Reference in New Issue
Block a user