feat: Deep consent verification — DataLayer, Storage, GCM, TCF
5 verification layers added to the 3-phase banner test:
1. DataLayer/GTM Interception: Proxy on window.dataLayer captures
all push() events. Distinguishes safe lifecycle events (gtm.js,
gtm.dom) from tracking events (page_view, conversion, purchase).
Flags tracking events before consent as violations.
2. localStorage/sessionStorage Monitoring: Intercepts setItem() to
detect tracking keys (_ga, _fbp, amplitude, mixpanel, etc.)
written before consent.
3. Google Consent Mode v2 Runtime Verification: Reads actual GCM
state (analytics_storage, ad_storage) per phase. Verifies
default=denied before consent, stays denied after reject,
switches to granted after accept.
4. TCF v2.2 State: Reads __tcfapi('getTCData') if available.
Verifies consent purpose states match user choice.
5. Cookie Attribute Analysis: Domain (1st vs 3rd party), expires
(>13 months), secure flag for tracking cookies.
10 new L2 checks with expert hints (EDPB, CNIL, §25 TDDDG).
All interceptor calls wrapped in try/except for graceful fallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -145,6 +145,24 @@ _TEXT_TO_CODE: list[tuple[str, str]] = [
|
||||
("drittanbieter.*dse", "third_party_dse_link"),
|
||||
("ohne vorherige einwilligung", "tracking_before_consent"),
|
||||
("trotz ablehnung", "tracking_after_reject"),
|
||||
("datalayer.*vor consent", "datalayer_events_before"),
|
||||
("datalayer.*vor einwilligung", "datalayer_events_before"),
|
||||
("localstorage.*tracking", "localstorage_tracking_before"),
|
||||
("storage.*tracking.*vor", "localstorage_tracking_before"),
|
||||
("consent mode.*runtime.*denied", "gcm_runtime_denied"),
|
||||
("gcm.*nicht denied", "gcm_runtime_denied"),
|
||||
("datalayer.*nach ablehnung", "datalayer_events_after_reject"),
|
||||
("consent mode.*bleibt", "gcm_stays_denied"),
|
||||
("gcm.*nach reject", "gcm_stays_denied"),
|
||||
("storage.*nach ablehnung", "storage_cleared_after_reject"),
|
||||
("tracking-cookie.*vor consent", "cookie_domain_check"),
|
||||
("cookie.*geschrieben.*vor", "cookie_domain_check"),
|
||||
("cookie.*13 monate", "cookie_expires_check"),
|
||||
("cookie.*ablauf.*ueber", "cookie_expires_check"),
|
||||
("tcf.*consent", "tcf_consent_valid"),
|
||||
("__tcfapi", "tcf_consent_valid"),
|
||||
("sendbeacon.*tracking", "response_blocked_before"),
|
||||
("beacon.*vor consent", "response_blocked_before"),
|
||||
]
|
||||
|
||||
|
||||
@@ -198,6 +216,17 @@ def _collect_violation_codes(scan: dict) -> dict[str, str]:
|
||||
if new_tracking_b and "tracking_after_reject" not in codes:
|
||||
codes["tracking_after_reject"] = ", ".join(new_tracking_b[:5])
|
||||
|
||||
# Deep verification violations (from consent interceptor)
|
||||
deep = scan.get("deep_verification", {})
|
||||
for phase_key in ("before_consent", "after_reject"):
|
||||
for v in deep.get(phase_key, {}).get("violations", []):
|
||||
raw_code = v.get("code", "")
|
||||
if not raw_code:
|
||||
continue
|
||||
# Map interceptor codes to banner check_keys
|
||||
check_key = _INTERCEPTOR_CODE_MAP.get(raw_code, raw_code)
|
||||
codes[check_key] = v.get("text", "")[:120]
|
||||
|
||||
return codes
|
||||
|
||||
|
||||
@@ -224,6 +253,16 @@ def _collect_pass_codes(scan: dict) -> dict[str, str]:
|
||||
return passes
|
||||
|
||||
|
||||
# Map consent_interceptor violation codes → banner check_keys
|
||||
_INTERCEPTOR_CODE_MAP: dict[str, str] = {
|
||||
"DL_TRACK_BEFORE_CONSENT": "datalayer_events_before",
|
||||
"STORAGE_TRACK_BEFORE_CONSENT": "localstorage_tracking_before",
|
||||
"GCM_NOT_DENIED_BEFORE_CONSENT": "gcm_runtime_denied",
|
||||
"DL_TRACK_AFTER_REJECT": "datalayer_events_after_reject",
|
||||
"GCM_NOT_DENIED_AFTER_REJECT": "gcm_stays_denied",
|
||||
"STORAGE_TRACK_AFTER_REJECT": "storage_cleared_after_reject",
|
||||
}
|
||||
|
||||
# Checks where absence of a violation means PASS (not "untested")
|
||||
# These are phase-based checks: if no tracking was detected, that's good.
|
||||
_ABSENCE_IS_PASS = {
|
||||
@@ -233,6 +272,17 @@ _ABSENCE_IS_PASS = {
|
||||
"google_consent_mode_defaults",
|
||||
"banner_language_mismatch",
|
||||
"cookie_wall",
|
||||
# Deep verification checks (absence = no violation found = PASS)
|
||||
"datalayer_events_before",
|
||||
"localstorage_tracking_before",
|
||||
"gcm_runtime_denied",
|
||||
"datalayer_events_after_reject",
|
||||
"gcm_stays_denied",
|
||||
"storage_cleared_after_reject",
|
||||
"cookie_domain_check",
|
||||
"cookie_expires_check",
|
||||
"tcf_consent_valid",
|
||||
"response_blocked_before",
|
||||
}
|
||||
|
||||
_TRACKING_COOKIE_PREFIXES = (
|
||||
|
||||
Reference in New Issue
Block a user