Application respects DNT signal; analytics disabled when DNT header set
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.
// 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.doNotTrackproperty 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 checksnavigator.doNotTrack === '1'or server-side checks for theDNT: 1request header before initializing analytics. -
Pass criteria: The application checks the DNT signal (either via
navigator.doNotTrackclient-side or theDNTheader server-side) and disables non-essential analytics and tracking when it is set to1. -
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:
// 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 — Data protection by design and by default — respecting user signals
- ccpa · §1798.120 — Consumer right to opt-out; Global Privacy Control (GPC) as a recognized opt-out signal
Taxons
History
- 2026-04-18·v1.0.0·Initial import from data-protection·automated