Last month I was debugging a tracking issue for a client and realized something uncomfortable: even after clearing all cookies and using a fresh incognito window, a third-party analytics script was still identifying the same user session. No cookies, no localStorage, no URL parameters. Just JavaScript reading properties that every browser willingly exposes.
Browser fingerprinting isn’t new, but most developers I talk to still underestimate how effective it is. The EFF’s Cover Your Tracks project found that 83.6% of browsers have a unique fingerprint. Not “somewhat unique” — unique. One in a million. And that number climbs to over 94% if you include Flash or Java metadata (though those are mostly dead now).
I spent a weekend building a fingerprinting test page to understand exactly what data points create this uniqueness. Here’s what I found.
The Canvas API: Your GPU’s Signature
This is the technique that surprised me most. The HTML5 Canvas API renders text and shapes slightly differently depending on your GPU, driver version, OS font rendering engine, and anti-aliasing settings. The differences are invisible to the eye but consistent across page loads.
Here’s a minimal Canvas fingerprint in 12 lines:
function getCanvasFingerprint() {
const canvas = document.createElement('canvas');
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);
return canvas.toDataURL();
}
That toDataURL() call returns a base64 string representing the rendered pixels. On my M2 MacBook running Chrome 124, this produces a hash that differs from the same code on Chrome 124 on my Intel Linux box. Same browser version, same text, different pixel output.
Why? The font rasterizer (FreeType vs Core Text vs DirectWrite), sub-pixel rendering settings, and GPU shader behavior all introduce tiny variations. A 2016 Princeton study tested this across 1 million browsers and found Canvas fingerprinting alone could identify about 50% of users — no cookies needed.
AudioContext: Your Sound Card Leaks Too
This one is less well-known. The Web Audio API processes audio signals with slight floating-point variations depending on the hardware and driver stack. You don’t even need to play a sound:
async function getAudioFingerprint() {
const ctx = new (window.OfflineAudioContext ||
window.webkitOfflineAudioContext)(1, 44100, 44100);
const oscillator = ctx.createOscillator();
oscillator.type = 'triangle';
oscillator.frequency.setValueAtTime(10000, ctx.currentTime);
const compressor = ctx.createDynamicsCompressor();
compressor.threshold.setValueAtTime(-50, ctx.currentTime);
compressor.knee.setValueAtTime(40, ctx.currentTime);
compressor.ratio.setValueAtTime(12, ctx.currentTime);
oscillator.connect(compressor);
compressor.connect(ctx.destination);
oscillator.start(0);
const buffer = await ctx.startRendering();
const data = buffer.getChannelData(0);
// Hash the first 4500 samples
return data.slice(0, 4500).reduce((a, b) => a + Math.abs(b), 0);
}
The resulting float sum varies by sound card and audio driver. Combined with Canvas, you’re looking at roughly 70-80% unique identification rates. I tested this on three machines in my homelab — all running Debian 12, all with different audio chipsets — and got three distinct values.
WebGL: GPU Model Exposed
WebGL goes further than Canvas. It exposes your GPU vendor, renderer string, and supported extensions. Most browsers serve this up without hesitation:
function getWebGLInfo() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
return {
vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),
renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL),
extensions: gl.getSupportedExtensions().length
};
}
On my machine this returns Apple / Apple M2 / 47 extensions. That renderer string alone narrows the pool considerably. Combine it with the exact list of supported extensions (which varies by driver version) and you’ve got another high-entropy signal.
The Full Fingerprint Stack
A real fingerprinting library like FingerprintJS (now Fingerprint.com) combines 30+ signals. Here are the ones that contribute the most entropy, based on my testing:
| Signal | Entropy (bits) | How It Works |
|---|---|---|
| User-Agent string | ~10 | OS + browser + version |
| Canvas hash | ~8-10 | GPU + font rendering |
| WebGL renderer | ~7 | GPU model + driver |
| Installed fonts | ~6-8 | Font enumeration via CSS |
| Screen resolution + DPR | ~5 | Monitor + scaling |
| AudioContext | ~5-6 | Audio processing differences |
| Timezone + locale | ~4 | Intl API |
| WebGL extensions | ~4 | Supported GL features |
| Navigator properties | ~3 | hardwareConcurrency, deviceMemory |
| Color depth + touch support | ~2 | display.colorDepth, maxTouchPoints |
Add those up and you’re at roughly 54-63 bits of entropy. You need about 33 bits to uniquely identify someone in a pool of 8 billion. We’re at nearly double that.
What Actually Defends Against This
I tested four approaches on my fingerprinting test page. Here’s my honest assessment:
Brave Browser — Best out-of-the-box protection. Brave randomizes Canvas and WebGL output on every session (adds noise to the rendered pixels). AudioContext gets similar treatment. In my tests, Brave generated a different fingerprint hash on every page load. The tradeoff: some sites that rely on Canvas for legitimate purposes (online editors, games) may behave slightly differently. I haven’t hit real issues with this in daily use.
Firefox with privacy.resistFingerprinting — Setting privacy.resistFingerprinting = true in about:config spoofs many signals: timezone reports UTC, screen resolution reports the CSS viewport size, user-agent becomes generic. It’s effective but aggressive — it broke two web apps I use daily (a video editor and a mapping tool) because they relied on accurate screen dimensions.
Tor Browser — The gold standard. Every Tor user presents an identical fingerprint by design. Canvas, WebGL, fonts, screen size — all normalized. But you’re trading performance (Tor routing adds 2-5x latency) and compatibility (many sites block Tor exit nodes).
Browser extensions (Canvas Blocker, etc.) — Ironically, these can make you more unique. If 0.1% of users run Canvas Blocker, and it alters your fingerprint in a detectable way (which it does — the blocking itself is a signal), you’ve just moved from “one in a million” to “one in a thousand who runs this specific extension.” I stopped recommending these after seeing the data.
A Practical Test You Can Run Right Now
Visit Cover Your Tracks (EFF) — it runs a fingerprinting test and shows exactly how unique your browser is. Then visit BrowserLeaks.com for a more detailed breakdown of each signal.
When I ran both tests in Chrome, my fingerprint was unique among their entire dataset. In Brave, it was shared with “a large number of users.” That difference matters.
What Developers Should Do
If you’re building analytics or auth systems, understand that fingerprinting exists in a gray area. The GDPR considers device fingerprints personal data (Article 29 Working Party Opinion 9/2014 explicitly says so). California’s CCPA covers “unique identifiers” which includes fingerprints.
My recommendation for developers:
- Don’t roll your own fingerprinting for tracking. If you need fraud detection, use a service like Fingerprint.com that handles consent and compliance.
- Test your own sites with the Canvas fingerprint code above. If you’re embedding third-party scripts, check what data they’re collecting. I found three tracking scripts on a client’s site that were fingerprinting users without disclosure.
- For your own browsing, switch to Brave. It’s Chromium-based so everything works, and the fingerprint randomization is on by default. I moved my daily driver six months ago and haven’t looked back.
If you’re working from a home office or homelab and want to take your privacy setup further, a dedicated mini PC running a DNS-level blocker like Pi-hole or AdGuard Home catches a lot of the fingerprinting scripts at the network level before they even reach your browser. I run one on my TrueNAS box and it blocks about 30% of tracking domains across my whole network. Full disclosure: affiliate link.
If you want to test what your browser reveals, I built a set of privacy-focused browser tools that run entirely client-side — no data ever leaves your machine. The PixelStrip tool handles EXIF/metadata stripping, and the diff checker compares text without uploading to a server.
The uncomfortable truth is that cookies were the easy privacy problem. The browser itself — its GPU, its fonts, its audio stack — is the harder one. And most people don’t even know they’re being identified.
📡 Join Alpha Signal for free market intelligence — I share daily analysis on tech sector moves and trading signals.
📧 Get weekly insights on security, trading, and tech. No spam, unsubscribe anytime.
Leave a Reply