# Analytics configured with IP masking; no user IDs sent without explicit consent

- **Pattern:** `ab-000850` (`data-protection.compliance-documentation.analytics-privacy-configured`)
- **Severity:** info
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000850
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000850)

## Why it matters

GDPR Art. 25 (data protection by design) requires that analytics configurations minimize data collection by default — and the default GA4 configuration does not do this. Without Consent Mode v2 and `analytics_storage: 'denied'` as the default state, GA4 collects full IP addresses and sets cookies before any user consent is obtained. ePrivacy Art. 5(3) makes this unlawful for EU users without prior consent. ISO-27001:2022 A.5.34 requires that privacy controls be applied at the point of processing, not as an afterthought. Passing raw user email to `analytics.identify()` also creates a direct PII leak to a third-party processor — this is an Art. 28 issue on top of the consent failure.

## Severity rationale

Info because misconfigured analytics is a procedural privacy failure rather than a direct security vulnerability, but regulators treat it as a consent mechanism failure and issue fines accordingly.

## Remediation

Initialize GA4 with Consent Mode v2 so analytics is denied by default and only activates after the user accepts in the consent banner.

```ts
// In your <head>, before the GA4 script tag loads:
window.dataLayer = window.dataLayer || []
function gtag(...args: unknown[]) { window.dataLayer.push(args) }

// Deny all by default
gtag('consent', 'default', {
  analytics_storage: 'denied',
  ad_storage: 'denied',
  ad_user_data: 'denied',
  ad_personalization: 'denied',
})

// Call after user accepts analytics in the consent banner:
gtag('consent', 'update', { analytics_storage: 'granted' })
```

For identity calls, pass a pseudonymous ID (see `pii-separation` pattern) never the raw email. If migrating is complex, consider switching to Plausible or Fathom — both are GDPR-compliant by design with no cookies, no IP storage, and no consent banner requirement for basic analytics.

## Detection

- **ID:** `analytics-privacy-configured`
- **Severity:** `info`
- **What to look for:** Enumerate every relevant item. Check the analytics service configuration in the codebase. For Google Analytics 4 (GA4): look for the `gtag` initialization in the HTML layout — verify that Consent Mode v2 is configured (`gtag('consent', 'default', ...)`) and that `analytics_storage: 'denied'` is the default until consent is granted. For GA Universal/GA3: check for `anonymize_ip: true` in the config. For Segment: verify that PII fields (email, name, phone) are not passed to `analytics.identify()`. For Mixpanel or Amplitude: check `identify()` calls. For Plausible or Fathom: these are privacy-friendly by design (no IP storage, no cookies) — confirm the correct tracking script is in use and check their configuration for custom domains.
- **Pass criteria:** At least 1 of the following conditions is met. Analytics service has IP masking or anonymization enabled (or uses a privacy-first tool like Plausible/Fathom that doesn't store IPs). User IDs sent to analytics are pseudonymous (UUID or hash), not email or name. Analytics scripts are only loaded/activated after the user grants analytics consent.
- **Fail criteria:** IP masking disabled in GA configuration. User email or name passed directly to analytics identify calls. Analytics loads before or without consent.
- **Skip (N/A) when:** Application does not use analytics.
- **Detail on fail:** Example: `"GA4 configured without Consent Mode v2. analytics_storage not set to 'denied' by default. Full IP addresses logged."` or `"Segment identify() called with user.email as the userId without consent check."`.
- **Remediation:** Configure analytics for privacy compliance:

  ```ts
  // GA4 Consent Mode v2 — initialize before gtag loads
  // In your <head>, before the GA4 script tag:
  window.dataLayer = window.dataLayer || []
  function gtag(...args: unknown[]) { window.dataLayer.push(args) }

  // Default: deny all until user consents
  gtag('consent', 'default', {
    analytics_storage: 'denied',
    ad_storage: 'denied',
    ad_user_data: 'denied',
    ad_personalization: 'denied',
  })

  // After user accepts analytics in consent banner:
  gtag('consent', 'update', { analytics_storage: 'granted' })
  ```

  For pseudonymous user IDs in all analytics tools, see the remediation for `pii-separation` check above. If budget allows, migrate to Plausible or Fathom — both are GDPR-compliant by design, require no consent banner for analytics, and provide simpler compliance posture.

---

## External references

- gdpr Art. 25
- gdpr Art. 32
- eprivacy Art. 5(3)
- iso-27001 A.5.34

Taxons: privacy-consent

HTML version: https://auditbuffet.com/patterns/ab-000850
