Privacy Tools Guide

Browser permission prompts represent an often-overlooked vector for user fingerprinting. While most users understand that websites can request access to their camera, microphone, or location, fewer realize that simply presenting these permission dialogs creates a unique behavioral fingerprint. This article examines how notification request APIs and other browser permissions enable tracking, with practical code examples and mitigation strategies.

Understanding Permission-Based Fingerprinting

When a website requests a browser permission, the browser displays a prompt asking the user to grant or deny access. The response to these prompts—and even the mere presence of certain permission requests—generates identifiable patterns that websites can exploit for tracking purposes.

Unlike traditional cookie-based tracking, permission fingerprinting operates without storing persistent data on the user’s device. Instead, it relies on the unique way browsers handle permission requests across different contexts, operating systems, and user behaviors.

The Notification Permission API as a Tracking Vector

The Notification API provides one of the most significant fingerprinting opportunities. When a site calls the request permission method, several factors create identifiable signals:

// Check current permission status
const currentStatus = Notification.permission;

// Request notification permission
Notification.requestPermission().then(permission => {
  console.log('Permission status:', permission);
});

The Notification.permission property returns one of three values: granted, denied, or default. This single value provides a coarse but useful tracking signal. However, the more insidious tracking occurs through behavioral analysis of how users respond to these prompts.

How Permission Requests Create Trackable Fingerprints

Prompt Response Patterns

Users who consistently deny permission requests present a different fingerprint than those who grant them. Websites can request multiple permissions and observe the pattern of grants and denials:

async function fingerprintPermissions() {
  const results = {};

  // Request various permissions and record responses
  const permissions = [
    'notifications',
    'geolocation',
    'camera',
    'microphone',
    'clipboard-read'
  ];

  for (const perm of permissions) {
    try {
      const result = await navigator.permissions.query({ name: perm });
      results[perm] = result.state;
    } catch (e) {
      results[perm] = 'unsupported';
    }
  }

  return results;
}

// Example output
// { notifications: 'default', geolocation: 'granted', camera: 'denied', ... }

This technique, sometimes called “permission fingerprinting,” creates a permission state vector that remains stable across sessions for many users. A user who denies camera access but allows location requests produces a different signature than one who reverses these choices.

Timing and Behavioral Signals

Beyond the permission states themselves, the timing and frequency of permission requests provide additional tracking data. Sites can measure:

// Measure response time to permission prompts
const startTime = Date.now();

Notification.requestPermission().then(permission => {
  const responseTime = Date.now() - startTime;

  // Combine response time with permission state for fingerprinting
  const fingerprint = {
    permission: permission,
    responseTime: responseTime,
    timestamp: new Date().toISOString()
  };

  // Send fingerprint data to analytics
  trackFingerprint(fingerprint);
});

Cross-Origin Permission State Leaks

Modern browsers attempt to isolate permission states between origins, but implementation differences create fingerprinting opportunities. The Permission API sometimes reveals states from related domains or subdomains, allowing trackers to build more complete profiles:

// Check if permissions granted on other domains leak through
async function checkCrossOriginPermissions() {
  // This query behavior varies across browsers
  const status = await navigator.permissions.query({
    name: 'notifications',
    // Some implementations may leak origin information
  });

  return status.state;
}

Real-World Tracking Implications

Persistence Without Storage

Unlike cookies that users can delete or ad blockers can remove, permission fingerprints persist until users manually change their browser settings. A user who granted notification permissions on one site carries that state to every subsequent site that requests the same permission.

This persistence makes permission fingerprinting particularly valuable for long-term tracking. Advertisers can combine permission states with other signals to create more persistent user profiles.

Device and Browser Fingerprinting

Permission capabilities vary significantly across devices and browsers. A mobile device running Safari presents different permission capabilities than a desktop Chrome installation. Trackers combine permission availability with other fingerprinting techniques:

// Combine permission fingerprinting with device capabilities
function comprehensiveFingerprint() {
  const fp = {
    // Permission states
    notifications: Notification.permission,
    geolocation: 'geolocation' in navigator,

    // Hardware capabilities
    hardwareConcurrency: navigator.hardwareConcurrency,
    deviceMemory: navigator.deviceMemory,

    // Browser details
    userAgent: navigator.userAgent,
    platform: navigator.platform
  };

  return fp;
}

Protecting Against Permission Fingerprinting

For Users

Several strategies help mitigate permission-based tracking:

  1. Standardize responses: Grant or deny permission requests consistently across sites to reduce the uniqueness of your permission pattern.

  2. Use browser privacy features: Firefox’s Enhanced Tracking Protection and Brave’s Shields include protections against permission fingerprinting.

  3. Review permissions regularly: Clear unwanted permissions through browser settings.

  4. Use containerization: Firefox Multi-Account Containers isolate permission states between contexts.

For Developers

Web developers can implement responsible permission handling:

// Responsible permission request pattern
async function requestNotificationPermission() {
  // Only request if not already determined
  if (Notification.permission === 'default') {
    // Use a clear, non-coercive UI pattern
    const shouldPrompt = await showUserFriendlyRequest();

    if (shouldPrompt) {
      return Notification.requestPermission();
    }
  }

  return Notification.permission;
}

// Respect user's existing decision
function initializeNotifications() {
  if (Notification.permission === 'granted') {
    enableNotificationFeatures();
  } else if (Notification.permission === 'denied') {
    // Don't repeatedly prompt - respect the denial
    showFallbackUI();
  }
}

Browser Vendor Responses

Browser vendors continue implementing protections against permission fingerprinting:

These protections evolve as vendors discover new fingerprinting techniques.

Built by theluckystrike — More at zovo.one