feat(cookie+routing): Storage-Typ-Filter + legal_notice capture-only
#3 Storage-Filter: cookie-check exponiert per-Cookie-Speichertyp (storage_inventory.per_cookie); CookieResultView bekommt Filter-Chips (Cookie/Local Storage/Framework …) + eine Speicher-Spalte, Anbieter ohne passenden Treffer werden ausgeblendet, KPI zeigt gefilterte Zahl. A-Routing: legal_notice ist jetzt ein kanonischer Doc-Type. Eigene Discovery-Regel (legal-disclaimer/rechtlicher-hinweis) VOR impressum → die Disclaimer-Seite wird nicht mehr als Impressum substituiert (Ursache, dass die Cross-Doc-Reconciliation nie zündete). capture-only: als doc_entry für B persistiert, aber nicht einzeln gescort (keine 0%-Noise, da ohne eigene Checkliste). Im Scan-Form als Option auswählbar. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -31,10 +31,18 @@ _compliance_check_jobs: dict[str, dict] = {}
|
||||
# a separate page. We check 'DSB benannt' as a sub-check of the DSE.
|
||||
_ALL_DOC_TYPES = [
|
||||
"dse", "impressum", "social_media", "cookie",
|
||||
"agb", "nutzungsbedingungen", "widerruf",
|
||||
"agb", "nutzungsbedingungen", "widerruf", "legal_notice",
|
||||
]
|
||||
|
||||
|
||||
# Capture-only doc types: erfasst + als doc_entry persistiert (für die
|
||||
# Cross-Doc-Reconciliation B), aber NICHT einzeln gescort. Sie haben keine
|
||||
# eigene Checkliste/MCs → _check_single würde nur eine irreführende 0%-Zeile
|
||||
# erzeugen. 'legal_notice' (Footer-„Rechtlicher Hinweis"/Disclaimer) trägt oft
|
||||
# VSBG/ODR-Aussagen, die Impressum-Pflichten erfüllen → wertvoll für B.
|
||||
_CAPTURE_ONLY = {"legal_notice"}
|
||||
|
||||
|
||||
# Human-readable labels per doc_type. Used in the report + emails.
|
||||
_DOC_TYPE_LABELS = {
|
||||
"dse": "Datenschutzerklaerung",
|
||||
@@ -77,8 +85,14 @@ _DISCOVERY_RULES: list[tuple[str, tuple[str, ...]]] = [
|
||||
"allgemeine-nutzungsbedingungen")),
|
||||
("dsb", ("datenschutzbeauftragt", "data-protection-officer",
|
||||
"dpo-contact", "/dsb")),
|
||||
# A: 'legal-disclaimer' (Footer-„Rechtlicher Hinweis") VOR impressum, damit
|
||||
# die Disclaimer-Seite NICHT mehr als Impressum substituiert wird (war die
|
||||
# Ursache, dass die Cross-Doc-Reconciliation nie zündete).
|
||||
("legal_notice", ("legal-disclaimer", "legal-disclaimer-pool",
|
||||
"rechtlicher-hinweis", "rechtliche-hinweise",
|
||||
"haftungsausschluss")),
|
||||
("impressum", ("impressum", "imprint", "legal-notice", "site-notice",
|
||||
"anbieterkennzeichnung", "legal-disclaimer-pool")),
|
||||
"anbieterkennzeichnung")),
|
||||
("dse", ("data-privacy", "datenschutz", "data-protection",
|
||||
"privacy-policy", "privacy-notice", "dsgvo",
|
||||
"data_privacy", "datenschutzinformation")),
|
||||
|
||||
@@ -17,6 +17,7 @@ from dataclasses import asdict
|
||||
|
||||
import httpx
|
||||
|
||||
from ._constants import _CAPTURE_ONLY
|
||||
from ._helpers import (
|
||||
_apply_profile_filter,
|
||||
_doc_type_label,
|
||||
@@ -117,6 +118,16 @@ async def run_phase_b(state: dict) -> None:
|
||||
))
|
||||
continue
|
||||
|
||||
# A: Capture-only — Text ist via doc_entries schon im Snapshot (für die
|
||||
# Cross-Doc-Reconciliation B); hier NICHT scoren (keine eigene
|
||||
# Checkliste → sonst irreführende 0%-Zeile).
|
||||
if doc_type in _CAPTURE_ONLY:
|
||||
results.append(DocCheckResult(
|
||||
label=label, url=url, doc_type=doc_type,
|
||||
error="Erfasst für Cross-Dokument-Abgleich (nicht einzeln bewertet).",
|
||||
))
|
||||
continue
|
||||
|
||||
pct = int(40 + (i / n_entries) * 40)
|
||||
_update(check_id, f"Pruefen {i+1}/{n_entries}: {label}...", pct)
|
||||
|
||||
|
||||
@@ -81,11 +81,15 @@ def build_storage_inventory(vendors: list[dict]) -> dict:
|
||||
"""Zählt je Speichertyp + liefert Beispiele für Nicht-Cookies."""
|
||||
by_type: dict[str, int] = {}
|
||||
examples: list[dict] = []
|
||||
per_cookie: dict[str, str] = {}
|
||||
for v in vendors or []:
|
||||
vname = v.get("name") or "?"
|
||||
for c in v.get("cookies") or []:
|
||||
st = detect_storage_type(c.get("name", ""), c.get("expiry", ""))
|
||||
by_type[st] = by_type.get(st, 0) + 1
|
||||
n = (c.get("name") or "").lower()
|
||||
if n:
|
||||
per_cookie[n] = st
|
||||
if st != "cookie" and len(examples) < 10:
|
||||
examples.append({
|
||||
"name": c.get("name", ""), "type": st, "vendor": vname,
|
||||
@@ -98,6 +102,8 @@ def build_storage_inventory(vendors: list[dict]) -> dict:
|
||||
"real_cookies": cookies,
|
||||
"other_storage": total - cookies,
|
||||
"examples": examples,
|
||||
# name_lower → Speichertyp (für den Frontend-Filter).
|
||||
"per_cookie": per_cookie,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ def test_inventory_counts_and_transparency_finding():
|
||||
tf = storage_transparency_finding(inv)
|
||||
assert tf and tf["type"] == "storage_transparency"
|
||||
assert "§ 25" in tf["control"]["article"]
|
||||
# per_cookie-Map (für den Frontend-Storage-Filter): name_lower → Typ.
|
||||
pc = inv["per_cookie"]
|
||||
assert pc["componentdefstorage__mutex_x"] == "framework_storage"
|
||||
assert pc["_ga"] == "cookie"
|
||||
assert pc["browserid1"] == "cookie"
|
||||
|
||||
|
||||
def test_no_finding_when_all_real_cookies():
|
||||
|
||||
Reference in New Issue
Block a user