fix(banner): Footer-Erreichbarkeit prüfen — kein HIGH/MEDIUM bei nicht-blockierendem Banner
User-Bug (BMW): Banner überlagert, aber Footer-Links (Impressum/DSE) bleiben klickbar → fehlender In-Banner-Link ist dann nur Best Practice, kein Verstoß. banner_text_checker misst per Playwright-trial-Klick, ob der Footer-Impressum/ DSE-Link trotz Banner erreichbar ist: erreichbar → LOW/Best-Practice (+ Borlabs- Consent-Historie-Hinweis), blockiert → HIGH/MEDIUM wie bisher. browser_cross_ finding: redundante (nicht footer-bewusste) "Link im Banner fehlt"-Befunde raus. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -126,24 +126,10 @@ def build_cross_findings(matrix: dict | None) -> list[dict]:
|
|||||||
"vom Tracking-Schutz des Browsers.",
|
"vom Tracking-Schutz des Browsers.",
|
||||||
})
|
})
|
||||||
|
|
||||||
# ── Oberfläche (Banner-Links) durchgängig fehlend ────────────────────
|
# Hinweis: Impressum/DSE-Link-im-Banner wird NICHT mehr hier als pauschales
|
||||||
if all(not _s(r).get("surface", {}).get("has_impressum_link") for r in data):
|
# MEDIUM geflaggt. Der per-Engine-Check (banner_text_checker) ist footer-
|
||||||
out.append({
|
# bewusst: ist der Footer-Link trotz Banner erreichbar → LOW/Best Practice
|
||||||
"title": "Impressum-Link im Banner fehlt (alle Browser)",
|
# statt Verstoss. Doppel-/Falsch-Flag hier vermieden.
|
||||||
"detail": "In keiner Engine ist aus dem Banner ein Impressum "
|
|
||||||
"erreichbar.",
|
|
||||||
"severity": "MEDIUM", "affected": _labels(data),
|
|
||||||
"measure": "Impressum-Link im Banner ergänzen (§ 5 DDG).",
|
|
||||||
})
|
|
||||||
if all(not _s(r).get("surface", {}).get("has_dse_link") for r in data):
|
|
||||||
out.append({
|
|
||||||
"title": "Datenschutz-Link im Banner fehlt (alle Browser)",
|
|
||||||
"detail": "In keiner Engine ist aus dem Banner die "
|
|
||||||
"Datenschutzerklärung erreichbar.",
|
|
||||||
"severity": "MEDIUM", "affected": _labels(data),
|
|
||||||
"measure": "Link zur Datenschutzerklärung im Banner ergänzen "
|
|
||||||
"(Art. 13 DSGVO).",
|
|
||||||
})
|
|
||||||
|
|
||||||
# ── Coverage-Hinweis: nicht getestete Browser ────────────────────────
|
# ── Coverage-Hinweis: nicht getestete Browser ────────────────────────
|
||||||
if missing:
|
if missing:
|
||||||
|
|||||||
@@ -279,6 +279,22 @@ def build_cname_cloaking_finding(found: list) -> Violation:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _footer_link_reachable(page, selector: str) -> bool:
|
||||||
|
"""True, wenn ein passender Link (Impressum/DSE im Footer) trotz offenem
|
||||||
|
Banner anklickbar ist — das Banner ueberlagert, blockiert die Seite aber
|
||||||
|
nicht. Playwright-trial-Klick = Aktionsbarkeits-Pruefung OHNE echten Klick;
|
||||||
|
schlaegt fehl, wenn ein blockierendes Overlay den Link abfaengt."""
|
||||||
|
try:
|
||||||
|
loc = page.locator(selector).first
|
||||||
|
if await loc.count() == 0:
|
||||||
|
return False
|
||||||
|
await loc.scroll_into_view_if_needed(timeout=1500)
|
||||||
|
await loc.click(trial=True, timeout=1500)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def check_banner_text(page) -> dict:
|
async def check_banner_text(page) -> dict:
|
||||||
"""Check cookie banner text for legal issues.
|
"""Check cookie banner text for legal issues.
|
||||||
|
|
||||||
@@ -378,13 +394,29 @@ async def check_banner_text(page) -> dict:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if not has_impressum:
|
if not has_impressum:
|
||||||
violations.append(Violation(
|
# Entscheidend: blockiert das Banner den Footer-Zugriff wirklich?
|
||||||
service="Cookie-Banner",
|
# Wenn der Footer-Impressum-Link trotz offenem Banner anklickbar ist
|
||||||
severity="HIGH",
|
# (Banner ueberlagert, blockiert aber nicht), ist der fehlende
|
||||||
text="Impressum nicht aus dem Cookie-Banner erreichbar. "
|
# In-Banner-Link nur Best Practice (LOW), kein Verstoss.
|
||||||
"Bei ueberlagerndem Banner muss ein Impressum-Link im Banner vorhanden sein (§5 TMG).",
|
if await _footer_link_reachable(
|
||||||
legal_ref="§5 TMG, LG Rostock Az. 3 O 22/19",
|
page, 'a[href*="impressum"], a[href*="imprint"]'):
|
||||||
))
|
violations.append(Violation(
|
||||||
|
service="Cookie-Banner", severity="LOW",
|
||||||
|
text="Impressum nicht direkt im Cookie-Banner verlinkt — es ist "
|
||||||
|
"jedoch ueber den Footer erreichbar (das Banner blockiert "
|
||||||
|
"die Seite nicht). Best Practice: Impressum- und DSE-Link "
|
||||||
|
"zusaetzlich direkt im Banner anbieten (Borlabs ermoeglicht "
|
||||||
|
"zudem eine sichtbare Einwilligungs-Historie).",
|
||||||
|
legal_ref="Best Practice (§5 TMG via Footer erfuellt)",
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
violations.append(Violation(
|
||||||
|
service="Cookie-Banner", severity="HIGH",
|
||||||
|
text="Impressum nicht erreichbar: kein Link im Banner UND das "
|
||||||
|
"ueberlagernde Banner blockiert den Footer-Zugriff. Dann "
|
||||||
|
"muss ein Impressum-Link im Banner vorhanden sein (§5 TMG).",
|
||||||
|
legal_ref="§5 TMG, LG Rostock Az. 3 O 22/19",
|
||||||
|
))
|
||||||
|
|
||||||
# Check 2: DSE link in banner
|
# Check 2: DSE link in banner
|
||||||
has_dse = any(
|
has_dse = any(
|
||||||
@@ -394,13 +426,24 @@ async def check_banner_text(page) -> dict:
|
|||||||
for l in banner_links
|
for l in banner_links
|
||||||
)
|
)
|
||||||
if not has_dse:
|
if not has_dse:
|
||||||
violations.append(Violation(
|
if await _footer_link_reachable(
|
||||||
service="Cookie-Banner",
|
page, 'a[href*="datenschutz"], a[href*="privacy"], a[href*="dsgvo"]'):
|
||||||
severity="MEDIUM",
|
violations.append(Violation(
|
||||||
text="Kein Link zur Datenschutzerklaerung im Cookie-Banner. "
|
service="Cookie-Banner", severity="LOW",
|
||||||
"Nutzer sollten vor der Einwilligung die DSE einsehen koennen.",
|
text="Datenschutzerklaerung nicht direkt im Banner verlinkt — sie "
|
||||||
legal_ref="Art. 13 DSGVO, ErwGr. 42 DSGVO (informierte Einwilligung)",
|
"ist jedoch ueber den Footer erreichbar (Banner blockiert "
|
||||||
))
|
"nicht). Best Practice: DSE-Link direkt im Banner anbieten, "
|
||||||
|
"damit Nutzer vor der Einwilligung informiert entscheiden.",
|
||||||
|
legal_ref="Best Practice (Art. 13 DSGVO via Footer erfuellt)",
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
violations.append(Violation(
|
||||||
|
service="Cookie-Banner", severity="MEDIUM",
|
||||||
|
text="Kein Link zur Datenschutzerklaerung im Cookie-Banner und das "
|
||||||
|
"Banner blockiert den Footer. Nutzer koennen vor der "
|
||||||
|
"Einwilligung die DSE nicht einsehen.",
|
||||||
|
legal_ref="Art. 13 DSGVO, ErwGr. 42 DSGVO (informierte Einwilligung)",
|
||||||
|
))
|
||||||
|
|
||||||
# Check 3: Wrong wording — "Zustimmung zur Datenschutzerklärung"
|
# Check 3: Wrong wording — "Zustimmung zur Datenschutzerklärung"
|
||||||
wrong_dse_consent_patterns = [
|
wrong_dse_consent_patterns = [
|
||||||
|
|||||||
Reference in New Issue
Block a user