DEV Community

Cover image for Browser Fingerprinting in 2026: What Still Works, What Doesn't
webdecoy
webdecoy

Posted on Originally published at webdecoy.com

Browser Fingerprinting in 2026: What Still Works, What Doesn't

Browser fingerprinting still works in 2026, but the useful techniques have shifted underneath everyone. Chrome's Privacy Sandbox has frozen or removed half the signals fingerprinting libraries depended on. Firefox and Safari added noise injection and API restrictions. Brave blocks attempts outright. Meanwhile headless browser frameworks have gotten dramatically better at spoofing what remains.

If you're building or maintaining a fingerprinting system, a lot of the advice from even two years ago is obsolete. This is a technical audit of what still produces usable signal, what's been neutralized, and what emerged to replace the losses.

One caveat up front: a fingerprint is not a durable cross-browser identity, and no single fingerprint should be treated as proof that a visitor is human or automated.

The Scoreboard

Each technique rated on two axes — entropy (how much identifying information it produces) and durability (how resistant it is to spoofing and browser mitigation).

Technique Entropy Durability Status in 2026
User-Agent string Very low None Dead. Frozen by Chrome 107+.
navigator.plugins None None Dead. Returns empty array in Chrome.
navigator.platform Very low None Dead. Frozen to generic values.
Canvas fingerprint Medium Medium Degraded but usable. Noise in Firefox/Brave.
WebGL fingerprint Medium-High Medium-High Still strong. Renderer strings remain diverse.
WebGL rendering Medium Medium Works. GPU output is hard to standardize.
AudioContext Medium Medium Works. Hardware timing differences persist.
Font enumeration Low-Medium Low Declining. OS font standardization.
Screen/display props Low Low Minimal entropy. Heavily spoofed.
Client Hints Low Low Reduced by design.
TLS fingerprint (JA4) High Very High Strongest signal. Cannot be spoofed from JS.
HTTP/2 settings Medium High Underutilized. Good connection-level entropy.
TCP/IP stack Low-Medium High Niche but durable.

The trend is clear: JavaScript-accessible signals are eroding. Network-level signals are ascendant. The most durable techniques in 2026 operate below the browser's API surface, where privacy extensions and stealth plugins can't reach them.

What's Dead

User-Agent string. Chrome killed it. Since Chrome 107 in late 2022 the string is frozen — the version number still increments, but OS version is pinned to Windows NT 10.0, platform details are generic, and it no longer differentiates minor versions or OS builds. Firefox and Safari followed. You can distinguish Chrome from Firefox from Safari, and that's about it. For bot detection it's worse than useless, because every automation framework sets whatever string it wants.

navigator.plugins / navigator.mimeTypes. Used to return arrays of installed plugins — a user with Flash 32.0.0.453, Java 8u281 and Chrome PDF Viewer had a distinct, slowly-changing signature. Chrome now returns a fixed generic array. Firefox the same. Remove these from any library that still checks them.

navigator.platform. Frozen to generic values like Win32 regardless of actual architecture. The Client Hints replacement (navigator.userAgentData.platform) is designed to be lower entropy and gates detailed values behind a permission request.

What's Degraded but Usable

Canvas fingerprinting

Draw a complex scene, read back the pixel data, hash it. GPU hardware, driver versions, font rendering and anti-aliasing produce slightly different output across devices.

Per-browser reality in 2026:

  • Chrome — still consistent, device-specific output. No noise injection. Highest-fidelity target.
  • Firefox — noise injection since 113 via privacy.resistFingerprinting. Off by default, on in strict privacy mode and private windows. When enabled, the same device produces a different hash on every page load.
  • Safari — minimal canvas protection. Still stable.
  • Brave — aggressively randomizes by default. Effectively useless against Brave users.
function getCanvasFingerprint() {
  const canvas = document.createElement('canvas');
  canvas.width = 256;
  canvas.height = 256;
  const ctx = canvas.getContext('2d');

  ctx.textBaseline = 'top';
  ctx.font = '14px Arial';
  ctx.fillStyle = '#f60';
  ctx.fillRect(125, 1, 62, 20);
  ctx.fillStyle = '#069';
  ctx.fillText('Browser fingerprint', 2, 15);
  ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
  ctx.fillText('Browser fingerprint', 4, 17);

  // Geometric shapes for GPU-dependent rendering
  ctx.beginPath();
  ctx.arc(50, 50, 50, 0, Math.PI * 2, true);
  ctx.closePath();
  ctx.fill();

  return canvas.toDataURL();
}
Enter fullscreen mode Exit fullscreen mode

Verdict: still works on roughly 80% of browsers. Expect continued degradation.

Font enumeration

Weakened for three reasons: OS standardization (Windows 11, recent macOS and modern Linux distros ship increasingly similar default sets), web font dominance (most modern sites never trigger system font rendering), and browser restrictions (privacy.resistFingerprinting returns a fixed list). Still distinguishes Windows from macOS from Linux. The days of font lists as a high-entropy identifier are over.

Screen and display properties

A 1920×1080 display at 1× describes tens of millions of devices. Trivially spoofed — Playwright and Puppeteer set arbitrary viewport sizes in one line. Include in a composite, don't rely on it.

What Still Works

WebGL — the quiet workhorse

Two levels. Parameter enumeration exposes hardware and driver information:

function getWebGLFingerprint() {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl');
  if (!gl) return null;

  const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');

  return {
    vendor: gl.getParameter(gl.VENDOR),
    renderer: gl.getParameter(gl.RENDERER),
    unmaskedVendor: debugInfo
      ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)
      : null,
    unmaskedRenderer: debugInfo
      ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
      : null,
    maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
    maxViewportDims: gl.getParameter(gl.MAX_VIEWPORT_DIMS),
    extensions: gl.getSupportedExtensions(),
    shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
  };
}
Enter fullscreen mode Exit fullscreen mode

The unmasked renderer string alone carries substantial entropy — ANGLE (NVIDIA GeForce RTX 4070 Ti Direct3D11 vs_5_0 ps_5_0) identifies a specific GPU model. Level 2 is render output: drawing a 3D scene and reading back pixels, with higher variability than canvas because 3D pipelines differ more across GPU architectures.

Why it's durable: browser vendors have been reluctant to restrict WebGL because doing so breaks legitimate applications — games, data visualizations, 3D product viewers, mapping. Injecting noise would break these visibly.

For bot detection specifically, WebGL is gold. Headless Chrome in a cloud VM reports the VM's virtual GPU — typically Google SwiftShader or llvmpipe — instantly distinguishable from any real user GPU. Even BaaS platforms that spoof this value struggle to replicate the full constellation of parameters a real GPU produces.

AudioContext

Exploits hardware-dependent differences in audio signal processing. Route an OscillatorNode through a DynamicsCompressorNode, read the output, and you get floating-point sample values that vary with the audio hardware and driver stack:

function getAudioFingerprint() {
  return new Promise((resolve) => {
    const context = new OfflineAudioContext(1, 44100, 44100);
    const oscillator = context.createOscillator();
    oscillator.type = 'triangle';
    oscillator.frequency.setValueAtTime(10000, context.currentTime);

    const compressor = context.createDynamicsCompressor();
    compressor.threshold.setValueAtTime(-50, context.currentTime);
    compressor.knee.setValueAtTime(40, context.currentTime);
    compressor.ratio.setValueAtTime(12, context.currentTime);
    compressor.attack.setValueAtTime(0, context.currentTime);
    compressor.release.setValueAtTime(0.25, context.currentTime);

    oscillator.connect(compressor);
    compressor.connect(context.destination);
    oscillator.start(0);

    context.startRendering().then((buffer) => {
      const data = buffer.getChannelData(0);
      let sum = 0;
      for (let i = 4500; i < 5000; i++) sum += Math.abs(data[i]);
      resolve(sum);
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

Lower entropy than WebGL, but it's an independent signal that's hard to spoof because it depends on the actual audio processing pipeline rather than a JavaScript property. Headless browsers often have no audio stack at all, or a software implementation producing output that matches no real desktop configuration.

TLS fingerprinting (JA4)

The biggest shift in fingerprinting since canvas was discovered. TLS fingerprinting doesn't operate in JavaScript at all. It analyzes the ClientHello sent during the HTTPS handshake — before page content loads, before JavaScript executes, before any browser API can be manipulated.

The ClientHello contains supported cipher suites in preference order, TLS extensions and their order, supported groups, signature algorithms, and ALPN protocols. JA4 hashes these into a fingerprint identifying the TLS stack implementation. Critically: you cannot change your JA4 fingerprint from JavaScript. It's determined by the TLS library compiled into the client.

Which means:

  • A Playwright bot claiming Chrome 126 but running an older Chromium has a JA4 that doesn't match real Chrome 126.
  • A Python requests session spoofing a Chrome User-Agent has a JA4 matching urllib3, not Chrome.
  • A BaaS platform running headless Chrome in the cloud has a JA4 matching their specific Chromium build — often months behind stable.

The cost: it requires server-side or proxy-level access to the raw handshake. You can't do it from JavaScript. But if you control the server, or use a CDN that exposes TLS metadata (Cloudflare exposes JA3 and JA4 in firewall rules), it's the most powerful identification signal available.

HTTP/2 settings

When a client opens an HTTP/2 connection it sends a SETTINGS frame — initial window size, max concurrent streams, header table size, enabled push. Different implementations choose different defaults, and like TLS it's determined by the client implementation rather than configurable from JavaScript. Underutilized, and worth adding.

The Anti-Fingerprinting Landscape

Chrome (Privacy Sandbox). Surgical rather than blunt: reduce entropy from each API rather than blocking it. Frozen UA, reduced Client Hints, deprecated plugins. WebGL renderer info stays exposed because removing it would break too many sites. The goal is to make fingerprinting less unique, not impossible.

Firefox. The most granular controls. privacy.resistFingerprinting (off by default, on in Tor Browser) adds canvas noise, restricts font enumeration, normalizes screen dimensions, limits timer precision. Standard Firefox blocks known fingerprinting scripts by domain without modifying API output.

Safari. Pragmatic — restricts some vectors (limiting document.fonts, reducing timer precision) but focuses mainly on cookie and storage partitioning.

Brave. The most aggressive. Randomizes canvas, blocks WebGL renderer info, adds AudioContext noise, limits fonts. The goal is to make fingerprinting actively unreliable, not just lower entropy.

On the other side:

Puppeteer Extra Stealth / Playwright stealth patches patch navigator.webdriver, spoof plugin arrays, override Chrome runtime properties. Increasingly they spoof WEBGL_debug_renderer_info to report a realistic GPU instead of SwiftShader. None of it affects TLS or network-level signals.

Browser-as-a-Service (Browserbase, Hyperbrowser) run real Chromium in the cloud, producing genuine fingerprints at the JavaScript level. Their weakness is TLS: the Chromium version they run often lags stable, creating a detectable mismatch between claimed UA version and actual JA4.

Anti-detect browsers (Multilogin, GoLogin, Dolphin Anty) are the hardest to detect, because they use modified real browser engines rather than automation frameworks. Detection requires ensemble methods — no single signal catches them.

Building a System in 2026

Layer 1 — network fingerprints (server-side). Collect at the proxy or load balancer:

TLS ClientHello   → JA4 hash
HTTP/2 SETTINGS   → settings fingerprint
TCP/IP characteristics → OS fingerprint
Enter fullscreen mode Exit fullscreen mode

This is the foundation because it cannot be manipulated from the client. It tells you what the client actually is, regardless of what it claims.

Layer 2 — hardware fingerprints (JavaScript). WebGL renderer + parameters, AudioContext output, canvas rendering. Harder to spoof than software properties because they depend on physical components and low-level driver behavior.

Layer 3 — behavioral fingerprints (JavaScript). Mouse movement patterns, scroll behavior, keystroke timing, touch events, interaction timing. Not traditional fingerprints, but the strongest bot/human discrimination available — a bot can spoof every static property perfectly and still fail here, because generating convincing human interaction at scale is an unsolved problem.

Cross-signal coherence is the whole game

No single technique is sufficient. The value is in the combination, and specifically in coherence. A client claiming Chrome 126 on Windows 11 should have:

  • a JA4 hash matching Chrome 126's TLS stack
  • a WebGL renderer matching a real Windows GPU (not SwiftShader)
  • AudioContext output consistent with Windows audio drivers
  • HTTP/2 settings matching Chrome's defaults
  • mouse movement with human-like jitter and velocity curves

If any one of these contradicts the others, something is being spoofed. A real browser is internally consistent. A spoofed browser almost never is, because each spoofing mechanism operates independently and rarely accounts for cross-signal dependencies.

The privacy tension

This has been written from a security perspective, deliberately. Fingerprinting for bot detection and fingerprinting for cross-site tracking are the same technology applied to different ends.

The privacy concerns are real — fingerprinting has been used to track users across sites without consent, circumventing cookie controls. The browser restrictions above are responses to documented abuse.

The security case is also real. Without fingerprinting, bots are nearly undetectable. CAPTCHAs fail. Rate limiting fails. Behavioral analysis alone has a high false-positive rate. The same signals that let an ad network track you across the web are the signals that let a payment processor catch a credential-stuffing attack.

This is a genuine tension, not a false dichotomy. The current trajectory — browsers restricting JavaScript-accessible signals while network-level fingerprinting becomes the primary detection vector — is a reasonable compromise. It makes cross-site tracking harder (you can't read TLS fingerprints from JavaScript) while preserving the ability of server operators to identify suspicious clients hitting their own infrastructure.

Where this lands in three years is anyone's guess. For now: the toolkit is smaller than it was, the signals that remain are more durable than the ones that were lost, and the arms race shows no sign of slowing.

Anyone here still getting useful entropy out of canvas, or have you moved everything to the network layer?


Originally published at webdecoy.com.

Related reading:

Top comments (0)