feat(audit): P36 Social-Media-Einbindungs-Check
CI / branch-name (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 18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / detect-changes (push) Successful in 9s
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / test-python-backend (push) Successful in 44s
CI / test-python-document-crawler (push) Has been skipped
CI / branch-name (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 18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / detect-changes (push) Successful in 9s
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / test-python-backend (push) Successful in 44s
CI / test-python-document-crawler (push) Has been skipped
check_social_embedding: erkennt direkte FB/Insta/Twitter/YouTube- Embeds (connect.facebook.net, platform.twitter.com etc) vs Heise-Shariff vs 2-Klick-Loesungen (Embetty). Direkte Embeds ohne Schutz = HIGH (EuGH C-40/17 Fashion-ID — der Site-Betreiber wird zum gemeinsam Verantwortlichen und braucht Einwilligung VOR dem Drittanbieter-Call). Shariff oder 2-Klick erkannt = INFO (positives Signal). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,26 @@ _JC_PATTERNS = (
|
|||||||
"gemeinsame verarbeitung",
|
"gemeinsame verarbeitung",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# P36 — Social-Media-Einbindung:
|
||||||
|
# "direct" = direkte FB/Insta/Twitter-Embeds laden bei Page-Load
|
||||||
|
# (HIGH-Risiko, Cookies vor Consent).
|
||||||
|
# "shariff" = Heise-Shariff-Buttons (clientseitig, kein 3rd-party-Call).
|
||||||
|
# "two_click" = zweistufige Loesung (Klick auf Platzhalter laed Tracker).
|
||||||
|
_SOCIAL_DIRECT_PATTERNS = (
|
||||||
|
"connect.facebook.net", "platform.twitter.com",
|
||||||
|
"platform.instagram.com", "platform.linkedin.com",
|
||||||
|
"youtube.com/embed", "syndication.twitter.com",
|
||||||
|
"//www.facebook.com/", "fb-pixel", "facebook-pixel",
|
||||||
|
)
|
||||||
|
_SOCIAL_SHARIFF_PATTERNS = (
|
||||||
|
"shariff", "ct_shariff", "data-shariff",
|
||||||
|
)
|
||||||
|
_SOCIAL_TWOCLICK_PATTERNS = (
|
||||||
|
"2-klick", "2klick", "zwei klick", "two-click",
|
||||||
|
"klick-zu-laden", "klick um zu laden", "platzhalter laed",
|
||||||
|
"embetty",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_save_only_reject(banner_result: dict) -> dict | None:
|
def check_save_only_reject(banner_result: dict) -> dict | None:
|
||||||
"""P35 — Banner hat keinen klaren Reject, nur "Speichern"."""
|
"""P35 — Banner hat keinen klaren Reject, nur "Speichern"."""
|
||||||
@@ -150,10 +170,67 @@ def check_jc_clause_in_dse(doc_texts: dict[str, str]) -> dict | None:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_social_embedding(
|
||||||
|
doc_texts: dict[str, str],
|
||||||
|
homepage_html: str | None = None,
|
||||||
|
) -> dict | None:
|
||||||
|
"""P36 — direkte Social-Embeds vs Shariff vs 2-Klick."""
|
||||||
|
sources: list[str] = []
|
||||||
|
for key in ("dse", "cookie", "impressum"):
|
||||||
|
v = (doc_texts or {}).get(key) or ""
|
||||||
|
if v:
|
||||||
|
sources.append(v[:50000])
|
||||||
|
if homepage_html:
|
||||||
|
sources.append(homepage_html[:50000])
|
||||||
|
if not sources:
|
||||||
|
return None
|
||||||
|
blob = " ".join(sources).lower()
|
||||||
|
direct_hits = [p for p in _SOCIAL_DIRECT_PATTERNS if p in blob]
|
||||||
|
has_shariff = any(p in blob for p in _SOCIAL_SHARIFF_PATTERNS)
|
||||||
|
has_twoclick = any(p in blob for p in _SOCIAL_TWOCLICK_PATTERNS)
|
||||||
|
|
||||||
|
if not direct_hits and not has_shariff and not has_twoclick:
|
||||||
|
return None
|
||||||
|
if direct_hits and not (has_shariff or has_twoclick):
|
||||||
|
return {
|
||||||
|
"severity": "HIGH",
|
||||||
|
"code": "social_direct_embed",
|
||||||
|
"label": "Direkte Social-Media-Embeds ohne 2-Klick-Schutz "
|
||||||
|
"oder Shariff erkannt",
|
||||||
|
"detail": (
|
||||||
|
f'Gefundene Drittanbieter-Skripte: '
|
||||||
|
f'{", ".join(sorted(set(direct_hits))[:4])}. '
|
||||||
|
"Diese laden i.d.R. Cookies/Pixel ohne Einwilligung. "
|
||||||
|
"Empfehlung: Heise-Shariff (clientseitig) oder "
|
||||||
|
"2-Klick-Loesung (Embetty, eigener Platzhalter)."
|
||||||
|
),
|
||||||
|
"legal_basis": "EuGH C-40/17 (Fashion-ID) — Einbinden eines "
|
||||||
|
"Facebook-Like-Buttons macht den Site-Betreiber "
|
||||||
|
"zum gemeinsam Verantwortlichen + benoetigt "
|
||||||
|
"Einwilligung VOR dem Drittanbieter-Call.",
|
||||||
|
}
|
||||||
|
if has_shariff or has_twoclick:
|
||||||
|
return {
|
||||||
|
"severity": "INFO",
|
||||||
|
"code": "social_protected_embed",
|
||||||
|
"label": (
|
||||||
|
"Datenschutzfreundliche Social-Media-Einbindung erkannt "
|
||||||
|
f"({'Shariff' if has_shariff else '2-Klick-Loesung'})"
|
||||||
|
),
|
||||||
|
"detail": (
|
||||||
|
"Drittanbieter-Skripte werden erst nach aktivem Klick "
|
||||||
|
"geladen — kein Tracking ohne Einwilligung."
|
||||||
|
),
|
||||||
|
"legal_basis": "EuGH C-40/17 + EDPB Guidelines 8/2020.",
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def run_all(
|
def run_all(
|
||||||
banner_result: dict | None,
|
banner_result: dict | None,
|
||||||
doc_texts: dict[str, str] | None,
|
doc_texts: dict[str, str] | None,
|
||||||
cookie_doc_missing: bool = False,
|
cookie_doc_missing: bool = False,
|
||||||
|
homepage_html: str | None = None,
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
findings: list[dict] = []
|
findings: list[dict] = []
|
||||||
try:
|
try:
|
||||||
@@ -174,6 +251,12 @@ def run_all(
|
|||||||
findings.append(f)
|
findings.append(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("P78 jc_clause failed: %s", e)
|
logger.warning("P78 jc_clause failed: %s", e)
|
||||||
|
try:
|
||||||
|
f = check_social_embedding(doc_texts or {}, homepage_html)
|
||||||
|
if f:
|
||||||
|
findings.append(f)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("P36 social_embedding failed: %s", e)
|
||||||
return findings
|
return findings
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user