Privacy Tools Guide

Websites use browser permission prompts (for notifications, camera, microphone, location) as fingerprinting vectors to track users by analyzing how quickly you respond to prompts, whether you grant or deny permissions, and which APIs are available on your browser. These behavioral fingerprints persist across sessions even when cookies are blocked, creating a unique identifier based on your permission handling patterns. You can defeat this by denying all permission requests, using permission management extensions, or enabling “Always ask for permissions” to randomize your patterns.

What Is Permission Prompt Fingerprinting

When a website requests access to browser features like notifications, camera, microphone, or location, the browser displays a permission prompt. This interaction creates several tracking opportunities:

  1. Permission state detection: Websites can query whether you’ve already granted or denied specific permissions
  2. Prompt behavior: How quickly you respond to prompts, whether you allow or block, and your interaction patterns
  3. API availability: The presence or absence of certain APIs can reveal browser configuration
  4. Browser-specific responses: Different browsers handle permission requests differently, creating distinguishing signals

Unlike traditional fingerprinting techniques that rely on hardware or software characteristics, permission prompt fingerprinting focuses on user behavior and browser state—making it particularly difficult to detect and block.

How the Notification API Enables Tracking

The Notification API is one of the most commonly exploited permission APIs for fingerprinting. Here’s how it works:

Checking Permission Status

// Check if notifications are already permitted
function getNotificationStatus() {
  if ('Notification' in window) {
    return Notification.permission;
  }
  return 'not-supported';
}

// Possible return values:
// 'default' - user hasn't been prompted yet
// 'granted' - user has allowed notifications
// 'denied' - user has blocked notifications

This simple check reveals whether a user has previously interacted with the notification prompt. Since users rarely clear permission states, this becomes a persistent tracking identifier.

Detecting Browser-Specific Behavior

Different browsers expose permission APIs differently:

// Detect browser vendor through permission API behavior
function detectBrowserViaPermissions() {
  const results = {
    hasNotification: 'Notification' in window,
    hasPermissions: 'permissions' in navigator,
    hasPush: 'PushManager' in window,
    notificationPermission: Notification.permission
  };

  // Different browsers return different combinations
  return results;
}

// Compare behavior across browsers
const perms = detectBrowserViaPermissions();
// Chrome: { hasNotification: true, hasPermissions: true, hasPush: true }
// Firefox: { hasNotification: true, hasPermissions: true, hasPush: true }
// Safari: { hasNotification: true, hasPermissions: false, hasPush: false }

Timing-Based Fingerprinting

The timing of your response to permission prompts creates a unique signature:

function fingerprintPermissionTiming() {
  const results = [];

  // Request notification permission and measure response time
  const startTime = performance.now();

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

    // Different users have different response times
    // Some immediately deny, others take time to read the prompt
    results.push({
      permission: permission,
      responseTimeMs: responseTime,
      timestamp: Date.now()
    });

    // Send fingerprint data to server
    sendFingerprintData(results);
  });
}

This timing data—when combined with other signals—creates a unique behavioral profile.

Other Permission APIs Used for Fingerprinting

Beyond notifications, several other permission APIs can be exploited:

Geolocation Permission

// Query geolocation permission status
function getGeoPermission() {
  if ('geolocation' in navigator) {
    return navigator.permissions.query({ name: 'geolocation' })
      .then(result => {
        return result.state; // 'granted', 'denied', or 'prompt'
      });
  }
  return Promise.resolve('not-supported');
}

Camera and Microphone

// Check camera/mic permission status
function getMediaPermissions() {
  const permissions = {};

  if ('mediaDevices' in navigator) {
    navigator.permissions.query({ name: 'camera' })
      .then(result => permissions.camera = result.state);

    navigator.permissions.query({ name: 'microphone' })
      .then(result => permissions.microphone = result.state);
  }

  return permissions;
}

Clipboard Access

// Clipboard permission reveals user behavior
function checkClipboardPermission() {
  if ('clipboard' in navigator) {
    return navigator.permissions.query({ name: 'clipboard-read' })
      .then(result => result.state);
  }
  return 'not-supported';
}

Combining Permission Signals for Unique Identification

The real power of permission fingerprinting comes from combining multiple signals:

function getCompletePermissionFingerprint() {
  const fingerprint = {
    notification: Notification.permission,
    geolocation: null,
    camera: null,
    microphone: null,
    clipboard: null,
    midi: null,
    persistentStorage: null,
    backgroundSync: null,
    notificationsEnabled: Notification.permission === 'granted'
  };

  const queries = [
    navigator.permissions.query({ name: 'geolocation' }),
    navigator.permissions.query({ name: 'camera' }),
    navigator.permissions.query({ name: 'microphone' }),
    navigator.permissions.query({ name: 'clipboard-read' }),
    navigator.permissions.query({ name: 'midi' }),
    navigator.permissions.query({ name: 'persistent-storage' }),
    navigator.permissions.query({ name: 'background-sync' })
  ];

  const names = ['geolocation', 'camera', 'microphone', 'clipboard',
                 'midi', 'persistentStorage', 'backgroundSync'];

  return Promise.all(queries).then(results => {
    results.forEach((result, index) => {
      fingerprint[names[index]] = result.state;
    });

    // Generate unique hash from combined permissions
    return hashPermissionFingerprint(fingerprint);
  });
}

function hashPermissionFingerprint(fp) {
  const combined = Object.values(fp).join('|');
  // Create hash for tracking
  let hash = 0;
  for (let i = 0; i < combined.length; i++) {
    const char = combined.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash;
  }
  return hash.toString(16);
}

This combined fingerprint can uniquely identify users because:

Real-World Tracking Techniques

Cross-Site Permission Harvesting

Malicious sites can build permission profiles by:

  1. Checking existing permission states for known services
  2. Requesting new permissions and tracking responses
  3. Correlating permission patterns across multiple domains
  4. Building user profiles based on permission similarities
// Example: Check if user has allowed common notification services
function checkNotificationServices() {
  const services = [
    { name: 'OneSignal', url: 'https://onesignal.com' },
    { name: 'Pushwoosh', url: 'https://pushwoosh.com' },
    { name: 'Airship', url: 'https://urbanairship.com' }
  ];

  // Attempt to detect which push services user has subscribed to
  services.forEach(service => {
    // Check localStorage for service-specific tokens
    const hasToken = localStorage.getItem(service.name.toLowerCase() + '_token');

    // Cross-reference with permission state
    if (Notification.permission === 'granted' && hasToken) {
      trackUserPreference(service.name);
    }
  });
}

Browser Extension Fingerprinting

Extensions can also be detected through permission prompts:

// Detect installed extensions by checking their permission requirements
function detectExtensionsViaPermissions() {
  const knownExtensions = [
    { id: 'cjpalhdlnbpafiamejdnhcphjbkeiagm', name: 'uBlock Origin' },
    { id: 'gighmmpiobklfepjocnamgkkbiglidom', name: 'AdBlock' },
    { id: 'nmioekflnmbiannmkhjbbplncbdcarge', name: 'Privacy Badger' }
  ];

  // Check if certain permissions are blocked by extensions
  const blocked = [];

  // Try to access APIs that extensions might block
  try {
    // Extensions often block certain fingerprinting APIs
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // If certain APIs fail or return unexpected results,
    // it may indicate extension interference
  } catch (e) {
    blocked.push('canvas');
  }

  return blocked;
}

How Browsers Are Responding

Browser vendors are aware of these tracking techniques and are implementing protections:

Chromium Changes

Firefox Protections

Safari Changes

How to Protect Yourself

Browser Settings

  1. Review Permission Settings Regularly
    • Chrome: Settings → Privacy and Security → Site Settings → Permissions
    • Firefox: Settings → Privacy & Security → Permissions
    • Safari: Preferences → Websites → Permissions
  2. Use Permission Managers
    • Install browser extensions that manage permissions
    • Use “ask” default for all permissions
  3. Clear Permission States
    • Periodically reset permissions for all sites
    • Use browser tools to view and manage granted permissions

Extension Protection

Several privacy extensions help block permission fingerprinting:

Browser Choice

Some browsers are more resistant to permission fingerprinting:

Detecting Permission-Based Tracking

To check if a site is using permission fingerprinting:

  1. Check Network Requests
    • Look for requests to analytics services with permission-related data
    • Monitor for unusual permission API queries
  2. Use Developer Tools
    • Check console for permission-related errors or warnings
    • Monitor JavaScript execution for permission queries
  3. Test with Fingerprinting Test Sites
    • Use sites likecovery.org or AmIUnique to see what permissions they can detect

Built by theluckystrike — More at zovo.one