Back to Blog
Fingerprint

Performance Timing Fingerprinting: How Hardware Signals Track You

How Performance.now() precision, hardware concurrency, and device memory become tracking signals, and how to control them at the engine level.

Introduction

The Performance API is one of the most fundamental browser interfaces, originally designed to help developers measure page load times, diagnose bottlenecks, and optimize user experience. Functions like performance.now(), performance.mark(), and performance.measure() give developers high-resolution timestamps for benchmarking code execution. Alongside these timing functions, properties like navigator.hardwareConcurrency and navigator.deviceMemory report the device's CPU core count and available RAM.

While these APIs serve legitimate purposes, they also expose hardware-specific characteristics that vary between devices. A laptop with 8 CPU cores and 16 GB of RAM produces measurably different timing results and hardware reports than a desktop with 16 cores and 32 GB. These differences remain stable across browser sessions, survive cookie clearing, and persist through incognito mode, making them reliable signals for tracking individual devices.

Privacy Impact

Performance timing signals represent a particularly insidious form of fingerprinting because they are deeply tied to physical hardware characteristics that users cannot easily change. Unlike cookies or local storage, which users can clear, timing signals emerge from the fundamental speed at which a device executes JavaScript, renders graphics, and processes data.

Research from institutions including Princeton and KU Leuven has demonstrated that timing-based fingerprinting can distinguish devices with high accuracy. A 2019 study showed that combining performance.now() measurements with hardware concurrency values could correctly identify returning visitors over 80% of the time, even when other identifiers were cleared.

The problem extends beyond individual signals. When a tracker collects navigator.hardwareConcurrency (CPU cores), navigator.deviceMemory (RAM), and microbenchmark execution times together, the combined fingerprint becomes highly distinctive. A device with 6 cores, 8 GB of memory, and a specific JavaScript execution speed profile narrows the identification window significantly. These signals require no user permissions, generate no prompts, and are invisible to the person being tracked.

Technical Background

To understand why performance timing varies between devices, it helps to look at what influences JavaScript execution speed and the APIs that expose hardware information.

Performance.now() and High-Resolution Time

performance.now() returns a high-resolution timestamp measured in milliseconds with microsecond precision. The actual resolution depends on the browser's security mitigations (many browsers reduce precision to prevent Spectre-class attacks), but even reduced precision reveals timing characteristics tied to the CPU's clock speed, cache hierarchy, and instruction pipeline.

When a website runs a microbenchmark, such as iterating a loop 100,000 times or computing SHA-256 hashes, the execution time is directly influenced by the device's hardware. A fast desktop CPU completes the same work faster than a mobile processor, and this difference is measurable and repeatable.

Hardware Concurrency

navigator.hardwareConcurrency returns the number of logical processor cores available to the browser. This value reflects the physical CPU configuration and does not change between sessions. Common values range from 2 (low-end mobile) to 32 or more (high-end workstations). The distribution is not uniform: certain core counts (4, 8, 12, 16) are far more common than others, which means unusual values become strong identifiers.

Device Memory

navigator.deviceMemory returns an approximate amount of device RAM in gigabytes, rounded to the nearest power of two (0.25, 0.5, 1, 2, 4, 8). While the rounding reduces precision, this value still contributes to the overall fingerprint. A device reporting 8 GB of memory combined with 8 cores is a different fingerprint than one reporting 4 GB with 4 cores.

Why These Values Matter Together

Individually, each signal has limited entropy. But combined, they form a hardware profile: a specific execution speed, a specific core count, and a specific memory class. When layered with other fingerprint signals like Canvas, WebGL, and fonts, timing characteristics contribute meaningfully to device identification.

Common Protection Approaches and Their Limitations

VPNs and Proxy Servers

VPNs change your IP address but have zero effect on performance timing. All timing measurements and hardware property queries happen locally within the browser. Two devices behind the same VPN still report entirely different hardware profiles and execution speeds.

Incognito and Private Browsing

Private browsing modes clear cookies and history but do not modify navigator.hardwareConcurrency, navigator.deviceMemory, or the execution speed of the underlying hardware. Your performance fingerprint in incognito is identical to your fingerprint in a normal window.

Browser Extensions

Extensions that attempt to modify hardware properties face fundamental limitations. They can override JavaScript property values by injecting scripts, but this creates inconsistencies that are straightforward to detect:

  • If navigator.hardwareConcurrency reports 4 cores but a Web Worker microbenchmark shows 8 parallel threads executing simultaneously, the values conflict.
  • If navigator.deviceMemory reports 2 GB but performance.now() timing for memory-intensive operations is consistent with 16 GB (no garbage collection pauses), the mismatch is apparent.
  • Extension-injected overrides can be detected by checking property descriptors, prototype chains, or by running code in a fresh context before extensions execute.

Randomization

Some privacy tools randomize timing values by adding noise to performance.now(). While this prevents stable identification, randomly shifting timestamps breaks legitimate performance monitoring and creates its own detectable pattern. A timestamp sequence with artificial jitter looks different from natural hardware variation.

BotBrowser's Engine-Level Approach

BotBrowser takes a fundamentally different approach to performance timing protection. Instead of intercepting API calls or adding noise after the fact, BotBrowser controls timing signals at the browser engine level to produce internally consistent results that match a complete device profile.

Profile-Based Hardware Identity

When you load a fingerprint profile, BotBrowser configures all hardware-related values to match that profile's target device:

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

This sets navigator.hardwareConcurrency and navigator.deviceMemory to the profile's configured values. These are not JavaScript overrides; they are applied at the engine level before any page code executes. Property descriptor checks, prototype inspection, and fresh-context evaluation all return the same values because the engine itself reports them.

Performance Timing Scale

BotBrowser provides the --bot-time-scale flag to control the apparent execution speed of JavaScript operations:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-time-scale=1.2

A scale value of 1.2 makes all performance.now() measurements appear 20% slower, simulating a less powerful device. A value of 0.8 makes them appear faster. The scaling is applied uniformly across all timing sources, so microbenchmarks, performance.mark(), and performance.measure() all produce internally consistent results that match the same hardware class.

Cross-Signal Consistency

The key advantage of engine-level control is consistency. When BotBrowser reports 4 CPU cores and 4 GB of memory through a profile, the timing characteristics of JavaScript execution also align with that hardware class. Web Workers execute with parallelism consistent with the reported core count. Memory allocation patterns match the reported memory class. There are no contradictions between what the browser reports and how it actually performs.

Deterministic Mode

For testing, research, and CI/CD pipelines, BotBrowser supports fully deterministic timing through the noise seed flag:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-time-scale=1.0 \
       --bot-noise-seed=42 \
       --user-data-dir="$(mktemp -d)"

The same profile, time scale, and noise seed produce identical timing fingerprints across sessions, host machines, and operating systems.

Configuration and Usage

Basic CLI Usage

chrome --bot-profile="/path/to/profile.enc" \
       --bot-time-scale=1.0 \
       --user-data-dir="$(mktemp -d)"

Playwright Integration

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
      '--bot-time-scale=1.0',
    ],
    headless: true,
  });

  const context = await browser.newContext({ viewport: null });
  const page = await context.newPage();

  await page.goto('https://example.com');
  // Hardware values and timing signals match the loaded profile
  await browser.close();
})();

Puppeteer Integration

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
      '--bot-time-scale=1.2',
      '--bot-noise-seed=42',
    ],
    headless: true,
    defaultViewport: null,
  });

  const page = await browser.newPage();
  await page.goto('https://example.com');
  await browser.close();
})();

Combined Configuration

For comprehensive protection, combine timing control with other profile settings:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-time-scale=1.0 \
       --bot-noise-seed=42 \
       --proxy-server="socks5://user:pass@proxy:1080" \
       --bot-config-timezone="America/New_York" \
       --bot-config-locale="en-US"

Verification

After launching BotBrowser with a profile, verify that hardware values and timing are controlled:

// Check hardware properties
console.log('CPU Cores:', navigator.hardwareConcurrency);
console.log('Device Memory:', navigator.deviceMemory, 'GB');

// Measure timing consistency
async function measureTiming() {
  const results = [];
  for (let i = 0; i < 5; i++) {
    const start = performance.now();
    for (let j = 0; j < 100000; j++) Math.sqrt(j);
    results.push(performance.now() - start);
  }
  return results;
}

const timings = await measureTiming();
console.log('Timing samples:', timings);
console.log('Variance:', Math.max(...timings) - Math.min(...timings));

What to check:

  1. navigator.hardwareConcurrency matches the profile's configured core count
  2. navigator.deviceMemory matches the profile's configured memory
  3. Timing samples show consistent behavior across runs with the same profile and seed
  4. Public fingerprint testing tools (CreepJS, BrowserLeaks) show no anomalies

Best Practices

  1. Always use a complete profile. Hardware values, timing, and other signals must all align. A profile ensures consistency across every fingerprint surface.

  2. Use --bot-time-scale intentionally. Set the scale to match the profile's target hardware class. A profile emulating a mobile device should use a higher scale value (slower execution), while a desktop profile may use 1.0 or lower.

  3. Use deterministic mode for CI/CD. Combine --bot-noise-seed with --bot-time-scale for reproducible results in automated testing pipelines.

  4. Match proxy geography to profile identity. A profile configured for a US-based device should use a US-based proxy. Timing consistency combined with mismatched geolocation weakens overall fingerprint coherence.

  5. Avoid mixing browser extensions that modify timing. BotBrowser handles all timing protection natively at the engine level. Third-party extensions that modify performance.now() or hardware properties will conflict.

Frequently Asked Questions

Does performance.now() precision differ between browsers?

Yes. Different browsers implement different levels of timestamp precision, partly as a mitigation against Spectre-class side-channel attacks. Chrome, Firefox, and Safari each reduce precision differently. BotBrowser's profile system accounts for the target browser's expected precision characteristics.

Can websites detect that timing values are being controlled?

If timing control is applied inconsistently (for example, modifying performance.now() but not Date.now() or performance.mark()), websites can detect the discrepancy. BotBrowser applies timing scaling uniformly across all timing sources at the engine level, preventing cross-API inconsistencies.

Does --bot-time-scale affect actual page performance?

No. The flag controls how timing values are reported, not how quickly JavaScript actually executes. Pages load and run at full speed; only the measurements reported to JavaScript reflect the scaled timing.

How does hardware concurrency interact with Web Workers?

BotBrowser ensures that the reported navigator.hardwareConcurrency value is consistent with the number of Web Workers that can run in parallel. The profile manages both values together.

Can I use different time scales for different profiles?

Yes. Each browser instance uses its own --bot-time-scale value. You can run multiple instances simultaneously with different profiles and different timing characteristics.

What about SharedArrayBuffer timing?

SharedArrayBuffer combined with Atomics can be used as a high-resolution timer. BotBrowser's engine-level timing control applies to shared memory timing channels as well, maintaining consistency across all timing measurement methods.

Do timing fingerprints survive browser updates?

On unprotected browsers, yes. The timing fingerprint is tied to hardware, not browser version. BotBrowser profiles are versioned, so you can maintain consistent timing characteristics across profile updates.

Summary

Performance timing APIs expose hardware characteristics that serve as stable tracking signals. performance.now(), navigator.hardwareConcurrency, and navigator.deviceMemory together form a hardware identity that persists across sessions and survives common privacy measures. BotBrowser controls all of these signals at the browser engine level through its profile system and --bot-time-scale flag, ensuring internally consistent results that match the target device identity. For related protection, see Canvas fingerprint protection, WebGL fingerprint control, and frame rate control.

#performance#timing#fingerprinting#privacy#cpu#hardware