Privacy Tools Guide

Websites use the Sensor API to collect accelerometer and gyroscope data, which creates unique device fingerprints by analyzing the specific biases and noise patterns of your device’s hardware sensors—information that persists even if you change location or reset your browser. Protect yourself by denying sensor permissions in your browser settings, using Firefox’s privacy.resistFingerprinting, enabling Tor Browser which blocks sensor access, or using a privacy extension that spoofs sensor data. This guide examines how sensor APIs work technically, demonstrates fingerprinting code examples, explains privacy implications, and provides practical mitigation strategies for developers and power users.

Understanding the Sensor API

The Sensor API provides a standardized interface for accessing device sensors through JavaScript. The most commonly exploited sensors for fingerprinting are:

Accessing sensor data requires user permission on modern browsers, but once granted, websites can collect continuous streams of readings.

How Sensor Data Creates Unique Fingerprints

Each physical device exhibits unique characteristics due to manufacturing tolerances, component variations, and wear patterns. These micro-differences manifest in sensor data as:

  1. Calibration offsets: Sensors rarely report perfect zero values when stationary
  2. Noise patterns: Thermal noise and electronic interference create unique baselines
  3. Frequency response: Slight variations in how each sensor responds to motion
  4. Axis alignment: Physical mounting of sensors introduces small misalignments

A 2018 study demonstrated that combining accelerometer and gyroscope data could uniquely identify devices with over 90% accuracy, even across different browsing sessions and after clearing cookies.

Code Example: Collecting Sensor Data

Here’s a practical example of how websites collect sensor data for fingerprinting:

// Check sensor availability
if ('Accelerometer' in window) {
  const accel = new Accelerometer({ frequency: 50 });

  accel.addEventListener('reading', () => {
    console.log('Accelerometer:', {
      x: accel.x,
      y: accel.y,
      z: accel.z,
      timestamp: Date.now()
    });
  });

  accel.start();
}

if ('Gyroscope' in window) {
  const gyro = new Gyroscope({ frequency: 50 });

  gyro.addEventListener('reading', () => {
    console.log('Gyroscope:', {
      x: gyro.x,
      y: gyro.y,
      z: gyro.z,
      timestamp: Date.now()
    });
  });

  gyro.start();
}

This code samples sensor data at 50Hz, collecting hundreds of readings during a typical page session.

Fingerprinting Techniques

Static Fingerprinting

The simplest approach collects sensor readings while the device is stationary. Even when completely still, sensors report small non-zero values due to:

These baseline readings create a relatively stable signature:

function collectBaseline(readings, duration = 5000) {
  return new Promise((resolve) => {
    const samples = [];
    const startTime = Date.now();

    const interval = setInterval(() => {
      samples.push({
        accel: { x: accel.x, y: accel.y, z: accel.z },
        gyro: { x: gyro.x, y: gyro.y, z: gyro.z },
        time: Date.now() - startTime
      });

      if (Date.now() - startTime >= duration) {
        clearInterval(interval);
        resolve(analyzeFingerprint(samples));
      }
    }, 20);
  });
}

function analyzeFingerprint(samples) {
  // Calculate mean, standard deviation, and min/max for each axis
  const stats = {};
  ['x', 'y', 'z'].forEach(axis => {
    const values = samples.map(s => s.accel[axis]);
    stats[`accel_${axis}`] = {
      mean: values.reduce((a, b) => a + b) / values.length,
      std: Math.sqrt(values.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / values.length)
    };
  });
  return stats;
}

Dynamic Fingerprinting

More sophisticated techniques induce specific motions and analyze the response. Common methods include:

// Example: Dynamic fingerprinting through controlled motion
function generateMotionSignature() {
  const readings = [];

  // Request user to rotate device
  return new Promise((resolve) => {
    let stage = 0;
    const stages = ['hold', 'rotate90', 'hold', 'rotate180', 'hold'];

    const motionHandler = (event) => {
      readings.push({
        rotationRate: {
          alpha: event.rotationRate.alpha,
          beta: event.rotationRate.beta,
          gamma: event.rotationRate.gamma
        },
        stage: stages[stage]
      });
    };

    // Cycle through orientations with visual cues
    const interval = setInterval(() => {
      stage++;
      if (stage >= stages.length) {
        clearInterval(interval);
        resolve(extractSignature(readings));
      }
    }, 2000);
  });
}

Privacy Implications

Sensor fingerprinting poses significant privacy concerns:

Mitigation Strategies

For Users

  1. Disable sensors: Restrict sensor access in browser settings
  2. Use privacy-focused browsers: Some browsers mock or randomize sensor data
  3. Permission management: Regularly review and revoke sensor permissions

For Developers

  1. Limit sampling frequency: Reduce data collection rates when possible
  2. Add noise: Introduce controlled randomness to sensor data
  3. Request minimal permissions: Only access sensors when genuinely needed
// Browser-level sensor permission request
navigator.permissions.query({ name: 'accelerometer' })
  .then(result => {
    if (result.state === 'granted') {
      // Implement privacy-preserving approach
    }
  });

Browser Implementations

Chrome, Firefox, and Safari have implemented varying levels of sensor protection:

Built by theluckystrike — More at zovo.one