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
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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user