feat(consent+report): P56-P67 Mercedes-Audit-Cycle (Anti-Audit, Phase G Vendors, Cookie-Behavior-Validator + 5 Mail-Polish-Items) [migration-approved]
CI / detect-changes (push) Successful in 11s
CI / branch-name (push) Has been skipped
CI / nodejs-build (push) Successful in 2m19s
CI / test-go (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (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 16s
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 / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 37s

P56  Anti-Auditing-Detection als constructive Compliance-Finding (Audit-API-
     Empfehlung statt Anklage, weil Mercedes berechtigt Bots blockiert)
P57  Phase G vendor_details Union mit cmp_vendors -> 42 Anbieter sichtbar
P58  Anti-Audit-Detection robuster (Script-Domain-Check + Settings-spezifisch)
P59  Cookie-Behavior-Validator (4 Layer, 3-Tier-Severity: MEDIUM=Kategorie-
     Mismatch / HIGH=Zweck-Mismatch / CRITICAL=beide=Vorsatz-Indiz)
     + Open Cookie Database (CC0) als Library-Seed (2264 Cookies)
P59b Cookie-Behavior in Banner-Check verdrahtet + Mail-Block (BUGFIX:
     SessionLocal selbst oeffnen, db war im Background-Task nicht im Scope)

Mail-Polish nach Mercedes-Review:
P63  Banner-Footer-Links auch im wb7-link/role=link erkennen (Shadow-DOM-
     Walker label-based statt nur <a href>)
P64  Re-Access-Severity: MEDIUM statt HIGH, wenn Footer "Einstellungen" oder
     Mercedes-typisch existiert; OEM-Footer-Detection (wb7-footer)
P65  Text-Truncation: Word-Boundary statt Zeichen-Cut (kein "einfa"-Bruch
     mehr in Sofortmassnahmen)
P66  GF-Aktionen: Service-Zweck vs Cookie-Zweck explizit erklaert
     (haeufige Verwechslung Marketing/GF: "Akamai-Beschreibung" != Cookie-
     Zweck pro DSK-OH 2024)
P67  Stirring-Finding mit "Verlust-Framing"-Erklaerung + Alt-vs-Neutral-
     Beispiel, statt nur EDPB-Fachbegriff

Compliance-Advisor FAQ (admin agent-core/soul):
  + CNIL/EDPB Top-Bussgelder (Google 100M, Meta 60M, Amazon 35M)
  + Deutsche Praezedenz (LG Muenchen Google Fonts, EuGH Planet49, BGH I ZR 7/16)
  + 4 Risiko-Pfade (Bussgeld/Abmahnung/Sammelklage/NOYB) + Berechnungs-Methodik

Document-Generator Templates: AGB-DE (142), Impressum (140), Widerrufs-
formular-Anlage (143), DSR-Process-Dedup (139), Cookie-Library (144).

Architektur: doc_action_mappings.py + banner_dom_walkers.py +
cookie_behavior_validator.py + vendor_detail_extractor.py rausgezogen,
um die 500-LOC-Caps in agent_doc_check_report.py und
banner_text_checker.py einzuhalten.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-21 06:28:25 +02:00
parent badb356740
commit 57c0f940a2
38 changed files with 3656 additions and 116 deletions
+90 -1
View File
@@ -214,6 +214,49 @@ async def detect_categories(page: Page, banner: BannerInfo) -> list[CategoryInfo
except Exception:
continue
# P22: Shadow-DOM-Fallback fuer Web-Component-CMPs (Mercedes cmm-cookie-banner).
# Sucht Checkboxes/Switches rekursiv durch alle shadowRoots.
if not categories:
try:
shadow_cats = await page.evaluate("""
() => {
const out = [];
function walk(root, depth) {
if (depth > 6) return;
for (const el of root.querySelectorAll('*')) {
if (el.shadowRoot) {
const sr = el.shadowRoot;
const inputs = sr.querySelectorAll('input[type=checkbox], [role=switch], [role=checkbox]');
for (const i of inputs) {
const lbl = (i.closest('label')?.textContent || i.getAttribute('aria-label') || '').trim();
if (lbl.length > 0) {
out.push({label: lbl.slice(0,60), host: el.tagName.toLowerCase()});
}
}
walk(sr, depth + 1);
}
}
}
walk(document, 0);
return out;
}
""")
for sc in (shadow_cats or []):
text_lower = sc["label"].lower()
for cat_name, keywords in CATEGORY_KEYWORDS.items():
if any(kw in text_lower for kw in keywords):
# Marker selector — toggling per shadow:cat:<label-pattern>
categories.append(CategoryInfo(
name=cat_name,
label=sc["label"][:50],
selector=f"shadow-toggle:{sc['label'][:50]}",
))
break
if categories:
logger.info("P22: %d shadow-DOM categories detected", len(categories))
except Exception as e:
logger.warning("Shadow-DOM category detection failed: %s", e)
# Generic fallback: search for toggle/checkbox elements with category keywords
if not categories:
try:
@@ -266,9 +309,55 @@ async def test_single_category(
scripts: list[str] = []
page.on("request", lambda req: _collect(req, scripts))
await page.goto(url, wait_until="networkidle", timeout=20000)
try:
await page.goto(url, wait_until="networkidle", timeout=20000)
except Exception:
await page.goto(url, wait_until="load", timeout=20000)
await page.wait_for_timeout(2000)
# P22: Shadow-DOM-Toggle fuer Web-Component-CMPs (Mercedes etc.)
if category.selector.startswith("shadow-toggle:"):
label_pat = category.selector[len("shadow-toggle:"):]
try:
await page.evaluate("""(pat) => {
const lbl = pat.toLowerCase();
function walk(root) {
for (const el of root.querySelectorAll('*')) {
if (el.shadowRoot) {
const inputs = el.shadowRoot.querySelectorAll(
'input[type=checkbox], [role=switch], [role=checkbox]');
for (const i of inputs) {
const t = (i.closest('label')?.textContent || i.getAttribute('aria-label') || '').toLowerCase();
if (t.includes(lbl) && !i.checked) { i.click(); return true; }
}
if (walk(el.shadowRoot)) return true;
}
}
return false;
}
walk(document);
}""", label_pat)
await page.wait_for_timeout(500)
# Save via accept-text "Speichern" / "Save" inside shadow
await page.evaluate("""() => {
const SAVE = /speichern|save|bestaetigen|confirm/i;
function walk(root) {
for (const el of root.querySelectorAll('*')) {
if (el.shadowRoot) {
for (const b of el.shadowRoot.querySelectorAll('button, [role=button]')) {
if (SAVE.test(b.textContent || '')) { b.click(); return true; }
}
if (walk(el.shadowRoot)) return true;
}
}
return false;
}
walk(document);
}""")
await page.wait_for_timeout(wait_ms)
except Exception as e:
logger.warning("Shadow-toggle for %s failed: %s", category.name, e)
config = CMP_CATEGORY_CONFIG.get(banner.provider)
if config: