# Application respects DNT signal; analytics disabled when DNT header set

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

## Why it matters

GDPR Art. 25 (privacy by design and default) supports honoring user privacy preferences expressed through browser controls, and California's SB-27 explicitly requires certain businesses to respect the Do Not Track signal. More broadly, a user who has configured their browser to send `DNT: 1` has expressed a clear privacy preference — loading analytics scripts regardless of that signal is a direct contradiction of the privacy-by-design principle. While DNT is not universally legally mandated, its absence in jurisdictions that do require it (some US states) creates a patchwork compliance gap. The implementation cost is one conditional check; the omission signals that user privacy preferences are not honored when they conflict with analytics objectives.

## Severity rationale

Info because DNT compliance is not universally legally mandated and requires no data breach to trigger, but in jurisdictions where it is required it is a standalone violation addressable with a single code change.

## Remediation

Check `navigator.doNotTrack` before initializing any non-essential analytics or tracking script. Wrap this in a helper in `lib/analytics.ts` so the check is applied consistently.

```ts
// lib/analytics.ts
function isDNTEnabled(): boolean {
  if (typeof navigator === 'undefined') return false
  return (
    navigator.doNotTrack === '1' ||
    (navigator as Navigator & { msDoNotTrack?: string }).msDoNotTrack === '1'
  )
}

export function initAnalytics() {
  if (isDNTEnabled()) return // respect user preference

  // Only initialize after consent AND DNT check pass
  const consent = JSON.parse(localStorage.getItem('cookie_consent') ?? '{}')
  if (!consent.analytics) return

  // GA4 / Segment / Amplitude initialization here
  gtag('consent', 'update', { analytics_storage: 'granted' })
}
```

Call `initAnalytics()` from your consent banner's save handler rather than unconditionally on page load. This satisfies both the consent gate and the DNT signal check with a single initialization path.

## Detection

- **ID:** `dnt-signal-respected`
- **Severity:** `info`
- **What to look for:** Enumerate every relevant item. Check whether the application reads the Do Not Track HTTP header or the JavaScript `navigator.doNotTrack` property before loading analytics and tracking scripts. The DNT header is sent by browsers when users enable "Do Not Track" in settings. While DNT is no longer legally mandated in most jurisdictions (W3C standardization stalled), respecting it is a privacy best practice and is required in some US state laws (e.g., California's SB-27). Look for code that checks `navigator.doNotTrack === '1'` or server-side checks for the `DNT: 1` request header before initializing analytics.
- **Pass criteria:** The application checks the DNT signal (either via `navigator.doNotTrack` client-side or the `DNT` header server-side) and disables non-essential analytics and tracking when it is set to `1`.
- **Fail criteria:** Application loads analytics and tracking regardless of the user's DNT preference. No DNT check found in analytics initialization code.
- **Skip (N/A) when:** Application has no analytics or non-essential tracking.
- **Detail on fail:** Example: `"No DNT signal handling found in codebase. Analytics (GA4, Hotjar) loads unconditionally regardless of user's DNT browser preference."`.
- **Remediation:** Check the DNT signal before loading analytics:

  ```ts
  // lib/analytics.ts — wrap analytics initialization

  function isDNTEnabled(): boolean {
    // Check browser DNT setting
    if (typeof navigator !== 'undefined') {
      return navigator.doNotTrack === '1' ||
        (navigator as Navigator & { msDoNotTrack?: string }).msDoNotTrack === '1'
    }
    return false
  }

  export function initAnalytics() {
    if (isDNTEnabled()) {
      console.debug('[analytics] DNT enabled — analytics not loaded')
      return
    }

    // Load GA4, Segment, etc. only if DNT is not set
    const script = document.createElement('script')
    script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`
    script.async = true
    document.head.appendChild(script)
  }
  ```

  Note: DNT is a user preference signal, not a legal requirement in most jurisdictions. It does not replace your consent banner under GDPR. The two mechanisms complement each other: the consent banner handles GDPR opt-in; DNT handles additional user signals.

---

## External references

- gdpr Art. 25
- ccpa §1798.120

Taxons: privacy-consent

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