feat(banner): Consent-Historie/Widerruf live erkennen (Borlabs-Stil, #62)
consent_history.detect_consent_history: erkennt CMP-Anbieter (Borlabs/ Usercentrics/OneTrust/Cookiebot/…) aus Storage+Cookies, versionierten Consent (historie-fähig) + dauerhaftes Widerruf-/Einstellungs-Widget. consent_scanner ruft es in Phase A; scan_matrix_summary surft summary.consent_history; browser_cross_finding: positiver Befund wenn vorhanden, sonst Best-Practice-LOW („Nutzer sehen, wann sie welcher Version zugestimmt haben"); BrowserBehaviorView zeigt es im Engine-Detail. Tests: 7 (classify/versioned) + 2 Cross-Finding. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -131,6 +131,38 @@ def build_cross_findings(matrix: dict | None) -> list[dict]:
|
||||
# bewusst: ist der Footer-Link trotz Banner erreichbar → LOW/Best Practice
|
||||
# statt Verstoss. Doppel-/Falsch-Flag hier vermieden.
|
||||
|
||||
# ── Consent-Historie / Widerruf (Best Practice, #62) ─────────────────
|
||||
ch_rows = [(_s(r).get("consent_history") or {}) for r in data]
|
||||
any_history = any(c.get("history_capable") for c in ch_rows)
|
||||
any_withdraw = any(c.get("withdraw_ui") for c in ch_rows)
|
||||
provider = next((c.get("provider") for c in ch_rows if c.get("provider")), "")
|
||||
if any_history or any_withdraw:
|
||||
bits = []
|
||||
if provider:
|
||||
bits.append(f"Anbieter: {provider}")
|
||||
if any_history:
|
||||
bits.append("versionierte Einwilligung (nachvollziehbar)")
|
||||
if any_withdraw:
|
||||
bits.append("Widerruf-/Einstellungs-Widget vorhanden")
|
||||
out.append({
|
||||
"title": "Einwilligungs-Historie / Widerruf vorhanden",
|
||||
"detail": "Positiv: " + ", ".join(bits) + ". Nutzer können "
|
||||
"nachvollziehen bzw. ändern, welcher Version sie zugestimmt "
|
||||
"haben.",
|
||||
"severity": "LOW", "affected": _labels(data),
|
||||
"measure": "Beibehalten.",
|
||||
})
|
||||
else:
|
||||
out.append({
|
||||
"title": "Keine sichtbare Einwilligungs-Historie",
|
||||
"detail": "Weder eine versionierte Consent-Historie noch ein "
|
||||
"dauerhaftes Widerruf-Widget erkannt.",
|
||||
"severity": "LOW", "affected": _labels(data),
|
||||
"measure": "Best Practice: Consent-Historie + jederzeit erreichbares "
|
||||
"Widerruf-/Einstellungs-Widget anbieten (Borlabs-Stil) — "
|
||||
"Nutzer sehen, wann sie welcher Version zugestimmt haben.",
|
||||
})
|
||||
|
||||
# ── Coverage-Hinweis: nicht getestete Browser ────────────────────────
|
||||
if missing:
|
||||
out.append({
|
||||
|
||||
@@ -8,7 +8,8 @@ from compliance.services.browser_cross_finding import build_cross_findings
|
||||
|
||||
|
||||
def _row(pid, label, engine, *, before=0, reject_ok=True,
|
||||
impressum=True, dse=True, with_summary=True, track_before=0):
|
||||
impressum=True, dse=True, with_summary=True, track_before=0,
|
||||
consent_history=None):
|
||||
if not with_summary:
|
||||
return {"profile_id": pid, "label": label, "engine": engine,
|
||||
"summary": None, "error": "launch failed"}
|
||||
@@ -22,6 +23,7 @@ def _row(pid, label, engine, *, before=0, reject_ok=True,
|
||||
# before_consent = nicht-essentielles Tracking vor Consent (das
|
||||
# rechtlich relevante Signal, NICHT die Cookie-Rohzahl `before`).
|
||||
"violations": {"before_consent": track_before},
|
||||
"consent_history": consent_history or {},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -99,5 +101,21 @@ def test_clean_matrix_no_violations():
|
||||
_row("chromium-headed-de", "Chromium", "blink"),
|
||||
_row("firefox-headed-de", "Firefox", "gecko"),
|
||||
]}
|
||||
# Alles sauber → keine Verstoß-Befunde (Impressum/DSE vorhanden).
|
||||
assert build_cross_findings(m) == []
|
||||
f = build_cross_findings(m)
|
||||
# Keine HIGH/MEDIUM-Verstöße; nur der Consent-Historie-Best-Practice-Hinweis (LOW).
|
||||
assert [x for x in f if x["severity"] in ("HIGH", "MEDIUM")] == []
|
||||
|
||||
|
||||
def test_consent_history_present_is_positive():
|
||||
ch = {"provider": "Borlabs", "history_capable": True, "withdraw_ui": True}
|
||||
m = {"browser_matrix": [
|
||||
_row("chromium-headed-de", "Chromium", "blink", consent_history=ch),
|
||||
]}
|
||||
hit = [x for x in build_cross_findings(m) if "Historie / Widerruf vorhanden" in x["title"]]
|
||||
assert hit and "Borlabs" in hit[0]["detail"]
|
||||
|
||||
|
||||
def test_consent_history_absent_is_best_practice_low():
|
||||
m = {"browser_matrix": [_row("chromium-headed-de", "Chromium", "blink")]}
|
||||
hit = [x for x in build_cross_findings(m) if "Keine sichtbare Einwilligungs-Historie" in x["title"]]
|
||||
assert hit and hit[0]["severity"] == "LOW"
|
||||
|
||||
Reference in New Issue
Block a user