Backend
Browser Fingerprinting in 2026: What Still Works, What Doesn't
webdecoy Dev.to (EN Zone)
4 views
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();
}
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),
};
}
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);
});
});
}
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
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:
JA4 Fingerprinting for AI Scraper Detection
Headless Browser Detection: Playwright, Puppeteer, Selenium
How to Detect Browser-as-a-Service Scrapers
Read original: https://dev.to/webdecoy/browser-fingerprinting-in-2026-what-still-works-what-doesnt-1hk9
← Previous
Why CAPTCHAs Are Dead (And What Replaces Them in 2026)
Next →
How to change reasoning effort in Codex CLI: model_reasoning_effort values and one-off overrides
Related
I Built a Python CLI Toolbox Instead of Writing One-Off Scripts
Backend
2
DEV Community
🚀 Crowdwide Just Became a KODA Season 2 Champion 👑
Backend
2
DEV Community
Sa-Token necessary implement StpInterface
Backend
2
DEV Community
New to Website building can you please help me, how much should I quote for this
Backend
6
Reddit r/webdev
Comments0
No comments yet — be the first