diff --git a/admin-compliance/app/sdk/agent/_components/CookieLibraryPanel.tsx b/admin-compliance/app/sdk/agent/_components/CookieLibraryPanel.tsx
index 2848a22c..b6b4c37b 100644
--- a/admin-compliance/app/sdk/agent/_components/CookieLibraryPanel.tsx
+++ b/admin-compliance/app/sdk/agent/_components/CookieLibraryPanel.tsx
@@ -16,6 +16,7 @@ export interface CookieFinding {
declared: string
library_purpose: string
remediation: string
+ control?: { control_id?: string | null; regulation?: string; article?: string }
}
interface CheckData {
@@ -71,6 +72,12 @@ export function CookieFindingList({ data }: { data: CheckData }) {
Library-Zweck: {f.library_purpose}
)}
{f.remediation}
+ {f.control?.regulation && f.control.regulation !== '—' && (
+
+ Rechtsgrundlage: {f.control.regulation} {f.control.article}
+ {f.control.control_id && ` · Control ${f.control.control_id}`}
+
+ )}
))}
diff --git a/backend-compliance/compliance/services/cookie_library_check.py b/backend-compliance/compliance/services/cookie_library_check.py
index 89842fe0..38566b86 100644
--- a/backend-compliance/compliance/services/cookie_library_check.py
+++ b/backend-compliance/compliance/services/cookie_library_check.py
@@ -22,6 +22,19 @@ from compliance.services.cookie_knowledge_db import lookup_cookie
_TRACKER_CATS = {"marketing", "statistics", "social_media", "targeting"}
+# A — auditfeste Verdrahtung: jeder Befund-Typ → echter Control (control_id aus
+# doc_check_controls) + legal_basis. Die Controls tragen regulation/article noch
+# NULL, daher liefern wir die Rechtsgrundlage hier strukturiert mit (bis sie in
+# den Controls gepflegt ist). Kette: Regulation → Article → Control → Finding.
+_CONTROL_MAP = {
+ "vague_duration": {"control_id": "AUTH-2051-A03", "regulation": "DSGVO", "article": "Art. 5 Abs. 1 lit. e + Art. 13"},
+ "excessive_lifetime": {"control_id": "AUTH-2051-A02", "regulation": "DSGVO", "article": "Art. 5 Abs. 1 lit. e"},
+ "tracker_as_necessary": {"control_id": "DATA-2851-A05", "regulation": "TDDDG", "article": "§ 25 Abs. 1"},
+ "missing_purpose": {"control_id": "AUTH-2053-A05", "regulation": "DSGVO", "article": "Art. 13"},
+ "third_country": {"control_id": "DATA-1624-A04", "regulation": "DSGVO", "article": "Art. 44 ff."},
+ "eu_alternative": {"control_id": None, "regulation": "—", "article": "kommerzielle Empfehlung"},
+}
+
def load_big_library(db, names: list[str]) -> dict:
"""Batch-Lookup der grossen Open-Cookie-Database (compliance.cookie_library,
@@ -219,6 +232,9 @@ def analyze_cookies(vendors: list[dict], big_lib: dict | None = None) -> dict:
),
})
+ # A: jeden Befund an seinen Control + Rechtsgrundlage haengen (auditfest).
+ for f in findings:
+ f["control"] = _CONTROL_MAP.get(f["type"], {})
findings.sort(key=lambda f: _SEV_ORDER.get(f["severity"], 3))
return {
"summary": {
diff --git a/backend-compliance/compliance/tests/test_cookie_library_check.py b/backend-compliance/compliance/tests/test_cookie_library_check.py
index da585dbc..2067154b 100644
--- a/backend-compliance/compliance/tests/test_cookie_library_check.py
+++ b/backend-compliance/compliance/tests/test_cookie_library_check.py
@@ -79,6 +79,21 @@ def test_excessive_lifetime():
assert el and "Art. 5" in el[0]["remediation"]
+def test_findings_carry_control_and_legal_basis():
+ # A: jeder Befund traegt control_id + Rechtsgrundlage (auditfest).
+ out = analyze_cookies([{
+ "name": "Google", "category": "necessary",
+ "cookies": [{"name": "_ga", "purpose": "x",
+ "expiry": "Wird solange gespeichert, bis es deaktiviert wird."}],
+ }])
+ assert out["findings"], "es sollte Befunde geben"
+ for f in out["findings"]:
+ assert "control" in f
+ vd = next(f for f in out["findings"] if f["type"] == "vague_duration")
+ assert vd["control"]["control_id"] == "AUTH-2051-A03"
+ assert "Art. 5" in vd["control"]["article"]
+
+
def test_vague_duration_flagged_concrete_ok():
# User-Beispiel Salesforce: "bis der Nutzer es deaktiviert" = vage.
out = analyze_cookies([{