The cross-browser fingerprint: identifying a device across Chrome, Firefox, and Safari
Open the same site in Chrome and in Firefox, on the same laptop, and conventional wisdom says you are two different visitors. The cookies do not cross. The user-agent strings differ. The canvas hash differs, because two browsers ship two rendering pipelines. For most of fingerprinting’s history that gap held, and the standard advice to a privacy-minded user was to keep separate browsers for separate identities. The question that broke this in 2017 was simple to state and uncomfortable to answer. What do two browsers on one machine still have in common, and is it enough to tie them together?
The answer is the hardware. The GPU is the same silicon no matter which browser asks it to draw a triangle. The CPU reports the same core count. The audio stack mixes the same way. The set of writing scripts the OS can render is identical. A browser is a thin layer of policy on top of an operating system and a machine, and if you can phrase a question so that the machine answers it rather than the browser, the answer is portable. The 2017 paper that made this concrete identified users across browsers at 99.24 percent accuracy on a single-browser baseline and 83.24 percent cross-browser, and it did so mostly by asking the GPU to render carefully chosen scenes and reading back the pixels.
This post is a deep read of that work and what came after. It starts with the distinction the paper drew between browser-level and hardware-level features, because that distinction is the whole idea. Then the WebGL rendering tasks, the heart of the technique, and why a rendered image survives a browser switch when a renderer string does not. Then the supporting signals, audio and CPU cores and installed languages, and the masking step that makes them comparable across two browsers. Then the numbers, feature by feature, from the paper’s own tables. Then the modern state in 2026: what Safari and Firefox now hide, where the technique still works, and the GPU-timing extension that pushed it further. The through-line is one claim worth holding onto: identity that lives below the browser is identity the browser cannot fully revoke.
The line between a browser feature and a hardware feature
Single-browser fingerprinting was a solved problem by the mid-2010s. AmIUnique, the dataset and tool that defined the state of the art, collected seventeen attributes (user-agent, plugin list, font list, screen resolution, timezone, canvas, and the rest) and identified the browser that produced them with high reliability. The catch is in the name. Those attributes fingerprint a browser instance, and most of them change the moment you switch browsers. Chrome and Firefox send different user-agents by construction. They ship different plugin and MIME tables. Their canvas output differs because the 2D rasterizers differ. A fingerprint built from those signals is, by design, browser-specific, and a user who switches browsers gets a fresh identity for free.
The 2017 work reframed the goal. Instead of asking what is unique about this browser, ask what is unique about the machine underneath it, and phrase the question through a browser API so the OS or the hardware supplies the answer. The authors split features into two layers. Browser-level features reflect a decision the browser made: the user-agent string, the way it serializes a canvas to a data URL, the order of its supported MIME types. Those are useless across browsers because each browser makes its own decisions. Hardware- and OS-level features reflect something below the browser: the GPU and its driver, the number of CPU cores, the audio hardware’s mixing behaviour, the writing-script libraries the operating system has installed. All browsers on one machine sit on top of the same OS and the same hardware, so a feature that genuinely reaches that layer reads the same regardless of which browser asked.
The trap is that an operation can touch both layers, and only the part that reaches the hardware is portable. The paper’s own example is sharp. When you render a textured cube in WebGL, the texture mapping is a GPU operation and reaches the hardware, but the step that decodes the resulting image into bytes is a browser operation. So the rendered pixels are cross-browser stable, but the encoded data URL is not, because Chrome and Firefox implement JPEG and PNG compression differently. The same logic rules out the canvas data URL that single-browser fingerprinting leaned on. To get a cross-browser signal, you have to read back the raw lossless pixels (a PNG, never a lossy JPEG or a browser-specific data URL serialization) and you have to choose operations whose output is decided by the GPU and not by the browser’s own code.
*The whole technique in one picture: signals that reach below the browser converge to a single value, while browser-policy signals stay separate.*The authors called this a 2.5-generation technique, sitting between second-generation single-browser fingerprinting and third-generation cross-device tracking. The motivation was not abstract. Their own survey of crowdsourced workers found that 70 percent used two or more browsers regularly on the same machine, and 13 percent used three or more. A tracking method that resets at the browser boundary was leaving most of those users with an easy escape, and the 2.5-generation fingerprint closed it.
The WebGL rendering tasks
WebGL is the load-bearing signal, and the reason is worth stating carefully because an earlier paper had reached the opposite conclusion. AmIUnique had tested WebGL and judged it too brittle and unreliable even for single-browser use. Its WebGL task drew a single uncontrolled scene and the result varied with canvas size, anti-aliasing, and other free variables, so the hash jumped around. The 2017 work’s contribution was to fix every one of those variables and then ask the GPU to do many specific, controlled jobs rather than one loose one. Render more than twenty distinct scenes, each probing a different corner of the graphics pipeline, with the canvas size, camera position, lighting, and ambient values all pinned to known constants. Hash each rendered image. The collection of hashes is the WebGL fingerprint, and because every parameter is fixed, the only thing left to vary the output is the GPU and its driver.
The tasks were chosen to exercise different parts of the rendering hardware. There are roughly thirty-one of them in the paper, labelled (a) through (r) with anti-aliased variants. A flavour of the set: a Suzanne monkey-head model rendered with a randomly generated per-pixel texture, because texture interpolation differs between graphics cards and a high-contrast random texture amplifies that difference; a cube with varying colours across its faces, to probe how the card interpolates values passed from the vertex shader to the fragment shader; line-and-curve tasks for 2D rasterization; alpha-transparency tasks sweeping eight alpha values from 0.09 to 1, because some cards quantize alpha into discrete steps and the jumps show up; lighting tasks with diffuse and specular point lights, because specular highlights are sensitive to the card’s floating-point behaviour; a complex-lights task that generates 5,000 metallic rings with two coloured light sources reflecting off each other; texture-format tasks for the compressed formats (DDS, PVR, float textures); and a video task that maps a short clip as an animating texture, because video decoding pulls in the driver and sometimes dedicated hardware.
*Why a rendered image crosses browsers but a renderer string is fragile: the pixels are the GPU's answer, read back losslessly so no browser-specific encoding step can perturb them.*The discipline around lossless read-back is the part that makes this cross-browser rather than just single-browser. The earlier canvas-fingerprinting line of work stored its image as a data URL, and the paper found that data URL encoding varies wildly across browsers. So does JPEG, which is lossy and implemented differently in each. PNG is lossless and the pixel values that come out are the pixel values the GPU produced. Read those raw, hash those, and Chrome and Firefox hand you the same digest from the same hardware. This single decision, lossless read-back over a browser-serialized image, is what moves WebGL from the single-browser column into the cross-browser one. If you want the single-vector treatment of how the same render leaks identity even within one browser, the companion piece on WebGL fingerprinting covers the renderer string and shader-precision angle that this work deliberately set aside.
One result from the paper deserves its own line, because it explains why this technique reads the image rather than the renderer string. The WebGL renderer string, the human-readable GPU name, is not cross-browser fingerprintable on its own. Firefox hid parts of it for privacy even in 2017, so the same machine reported one renderer name in Chrome and a poorer one in Firefox. The authors had to normalize the strings into a common format to get any cross-browser use out of them, and even normalized, the renderer name carried only 4.01 bits of cross-browser entropy at 37 percent cross-browser stability. The rendered images carried far more. The combined GPU tasks reached 7.10 bits of cross-browser entropy at 91 percent stability. The lesson is that behaviour beats labels. A label is something the browser chooses to report and can choose to blank. The pixels are what the hardware actually did, and that is much harder to fake convincingly.
The signals that hold the fingerprint together
WebGL does most of the work, but it does not work alone. Three other hardware- and OS-level signals fill in the gaps and, importantly, stay stable when WebGL wobbles.
The first is audio. The AudioContext API runs a small signal-processing graph: generate a waveform with an oscillator, push it through a dynamics compressor that squashes loud sounds and amplifies quiet ones, then read the result back in the frequency domain. Earlier audio-fingerprinting work had used exactly this graph for single-browser identification. The cross-browser twist is that the precise peak values and frequencies wobble slightly between two browsers on the same machine, so a raw comparison fails. The fix is to bin. Lay out a grid of small steps on both the frequency and value axes, and record only which bins the signal lands in, as a list of ones and zeros. That coarsening throws away the browser-specific jitter and keeps the hardware-specific shape, and the binned profile reads the same across browsers. On top of the processed wave, the technique also reads the audio device’s own properties: sample rate, maximum channel count, the input and output counts. Those describe the audio hardware directly. The AudioContext signal carried 1.87 bits of cross-browser entropy at 97 percent stability in the paper, which is modest entropy but very reliable. The deeper mechanics of the oscillator-to-compressor graph are in the dedicated AudioContext fingerprinting post.
The second is the CPU core count, read through navigator.hardwareConcurrency. The number of logical cores is a property of the machine and does not change when you switch browsers, so it is a clean cross-browser signal with 100 percent stability in the paper. There is one wrinkle that the technique had to correct for: Safari at the time halved the core count it reported to Web Workers, so a machine that read as eight cores in Chrome read as four in Safari, and the cross-browser comparison had to double Safari’s number back up before matching. When hardwareConcurrency is not supported by an older browser, the count can still be inferred through a side channel by spinning up workers and watching when the completion time jumps, the point where you have saturated the real cores. The core count carries little entropy on its own (most machines have 2, 4, or 8 cores) but it is free and perfectly stable, so it costs nothing to fold in. The same navigator-hardware tells, core count and device memory, get the full treatment in hardware concurrency and device memory.
The third is the set of installed writing scripts, which is the most operating-system-specific signal of the four. Languages like Chinese, Korean, and Arabic need special font libraries installed at the OS level to render, and the presence or absence of those libraries is a property of the OS install, not the browser. No browser exposes an API to list installed languages, so the technique reads them through a side channel: render a short string in each writing script and measure the result. If the script’s library is installed, the text renders; if not, the browser shows a row of fallback boxes, and the boxes are detectable. The paper tested 36 writing scripts drawn from Wikipedia and ranked by popularity, covering Latin, Chinese, Arabic, Devanagari, Cyrillic, and more. The rendered-images version of this signal carried about 1.98 bits of cross-browser entropy. It is a quiet but genuinely OS-level tell, because two browsers on the same Windows or macOS install see exactly the same set of language packs.
Making two browsers agree: the mask
Collecting hardware signals is half the job. The other half is reconciling them across two browsers that do not behave identically even at the hardware boundary. A browser might not support anti-aliasing, in which case every anti-aliased task produces garbage for it. One browser might use hardware rendering while the other falls back to software for the same scene. The technique handles this with a mask: a list of ones and zeros, one per task hash, that selects which signals to actually compare for a given browser pair.
For single-browser fingerprinting the mask is trivial, all ones, every signal counts. For cross-browser it is built from two sources. The first comes straight from capability detection: if a browser does not support anti-aliasing, every task that depends on anti-aliasing gets masked to zero, because its output is meaningless for that browser. The second is learned. For each pair of browsers (Chrome versus Firefox, Chrome versus Edge, and so on) the system trains a separate mask on a small slice of data, brute-forcing over candidate masks to find the one that maximizes cross-browser stability times uniqueness. The two quantities trade off against each other directly. Use only the rock-solid, always-identical features and stability hits 100 percent but uniqueness collapses, because everyone looks the same. Use every feature including the jittery ones and uniqueness is high but stability drops, because the same machine now reads slightly differently across browsers. The mask is the knob that picks the balance point per browser pair. This is the same tension that every commercial detector negotiates, treated in general terms in the entropy budget every detector balances.
*The mask is a per-browser-pair bitmask ANDed against the task hashes; only the agreeing tasks survive into the final fingerprint.*The reason this matters in practice is that it converts an unstable raw signal into a stable identifier without a central server having to know anything in advance. The same machine produces the same masked fingerprint in Chrome and Firefox because the mask has already excised the tasks where they would disagree. That is what lets a tracker on two different sites, seeing one browser each, decide the two visits came from one device.
What the numbers actually said
The headline figures are worth stating precisely because they are easy to garble. On a single-browser basis, against the same dataset, the technique identified 99.24 percent of users as unique, compared to 90.84 percent for AmIUnique, the prior state of the art. Cross-browser, it identified 83.24 percent of users as unique with 91.44 percent cross-browser stability. The only prior cross-browser method, Boda et al. from 2012, reached 68.98 percent uniqueness at 84.64 percent stability once its IP-address feature was excluded, and the authors excluded IP deliberately because IP addresses move with DHCP, NAT, mobile networks, and proxies and so are not a reliable identity anchor. The dataset behind these numbers was 3,615 fingerprints from 1,903 users, collected over three months through Amazon Mechanical Turk and Microworkers, with a ground-truth identifier planted in each worker’s URL so the system knew which fingerprints truly belonged to the same person.
The per-feature breakdown is where the design choices show their work. Reading from the paper’s feature table, here are the cross-browser entropy and stability figures for the signals that carried the technique:
*Cross-browser entropy and stability per feature, from the paper's own table. The GPU tasks combined dominate; the renderer string alone is weaker and far less stable.*A few of these readings carry a lesson. Notice that the normalized WebGL renderer string, the GPU’s printed name, sits at only 37 percent cross-browser stability, while the rendered GPU tasks reach 91 percent. The name is what a browser can blank for privacy; the pixels are what the hardware did. Notice too that the single most reliable high-entropy signal in the whole set is the timezone, at 3.51 bits and 100 percent stability, because it is genuinely a machine-level setting that no browser modifies and it does not change across a browser switch. Timezone, locale, and the Intl API as a cross-check get their own dedicated treatment. And the technique held up well when it lost any one feature: removing any single signal dropped the overall accuracy by at most 0.3 percent, because the entropy was spread across many partly-redundant tasks rather than concentrated in one.
One more observation from the paper closes the loop on why the GPU pixels are hard to fake. Software rendering does not erase the fingerprint. The authors pulled out the cases where Chrome had fallen back to Google’s SwiftShader software renderer, with no GPU involved, and found that even pure software rendering produced 7 distinct fingerprints across 11 cases. Lower entropy than hardware rendering, but not zero. And separately, the rendering result is a blend of software and hardware contributions in which the hardware contributes more. So even forcing software rendering, a tactic that flattens the renderer string, does not flatten the rendered pixels all the way down.
The state in 2026: what the browsers now hide
The 2017 work landed when most browsers exposed their hardware fairly openly. The years since have closed some doors, partly in direct response to research like this, and the cross-browser surface in 2026 looks different depending on which browser you are looking at.
Safari moved hardest on the WebGL renderer. WebKit blocked the WEBGL_debug_renderer_info extension around 2020, so Safari no longer hands out the specific GPU name and reports a generic Apple GPU instead. It also adds noise to canvas output. That removes the easy renderer-string leak, but it does not touch the deeper signal: Safari still has to render the WebGL scenes, and the pixels still come from the real GPU. A blanked name does not blank the image.
Firefox took a middle path. With privacy.resistFingerprinting enabled, Firefox disables the WEBGL_debug_renderer_info extension entirely and pins navigator.hardwareConcurrency to a constant 2 regardless of the real core count, alongside canvas noise, normalized screen resolution, and reduced font enumeration. Even with RFP off, Firefox shipped a sanitized renderer by default: starting in Firefox 92 it exposes a bucketed RENDERER string (a form like ANGLE (NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0) with the exact driver build stripped) rather than the raw unmasked value, the relevant bug being Mozilla bug 1722113. The bucketing coarsens the string into broad GPU categories rather than exposing exact driver details. Again, this is the label, not the pixels.
Chrome has been the most permissive of the three, still exposing WEBGL_debug_renderer_info and the full ANGLE renderer string by default in 2026, though the privacy debate around it continues. The practical consequence is that the cross-browser-stable part of the original technique, the rendered images and the hardware-property reads, still functions wherever WebGL renders at all, while the easy renderer-string shortcut has narrowed to Chrome. The Tor Browser remains the only mainstream browser that gets close to defeating the whole approach, and it does so the hard way: it disables canvas by default, normalizes most outputs, and removes many of the APIs the technique depends on. The paper’s own defense section pointed at exactly this, plus a heavier virtualization layer, and noted that the cleanest fix is to make the machine itself look generic rather than patching each API one at a time.
The technique also got stronger from the attacker’s side. In 2022 a fourteen-author team across Ben-Gurion University, the University of Lille, CNRS, Inria, and the University of Adelaide published DrawnApart at NDSS, and it pushed GPU fingerprinting from identifying a GPU model to identifying an individual GPU chip. The idea exploits manufacturing variation: the execution units inside a single GPU run at very slightly different speeds, a fixed quirk of how that specific piece of silicon came off the line. DrawnApart issues a sequence of short WebGL workloads aimed at different execution units and times how each one responds, then feeds the timing trace to a machine-learning model that maps it to an embedding vector unique to that chip. Across more than 2,500 crowdsourced devices it boosted median tracking duration by up to 67 percent over the prior state of the art. Crucially, it distinguishes machines with identical make, model, and software, which the 2017 technique could not. And because it reads execution-unit timing rather than a renderer name, the Safari and Firefox mitigations that hide the GPU string do nothing to stop it. The move to WebGPU compute shaders, which expose lower-level timing than WebGL, only sharpens that edge.
What survives the browser switch
The durable claim from this line of work is narrow and uncomfortable. Identity that lives below the browser is identity the browser cannot fully revoke. A user who keeps Chrome for work and Firefox for personal browsing, believing the two are separate, shares one GPU, one CPU, one audio stack, and one set of OS language packs between them, and a tracker that phrases its questions to reach that hardware reads one machine, not two. The 2017 numbers (99.24 percent single-browser, 83.24 percent cross-browser) put a figure on how leaky that boundary is, and the figure is high enough that switching browsers was never the privacy move people thought it was.
The mitigations that followed are real but partial, and the partiality is structural rather than incidental. A browser can blank the GPU’s printed name, and Safari and Firefox both do. It can pin the core count to a constant, and Firefox does. What it cannot easily do is refuse to render a WebGL scene, because real sites use WebGL, and the moment it renders, the pixels carry the hardware’s signature out into JavaScript. The renderer string is a label the browser owns and can lie about. The rendered image is the hardware’s own answer, and lying about that convincingly means faking how a specific GPU rounds floating point and interpolates textures across thousands of pixels, in a way that stays consistent with every other signal the page also collects. DrawnApart drove the point home by skipping the image entirely and reading the timing of the silicon, which no current browser mitigation addresses at all.
The honest reading of where this sits in 2026 is that the renderer-string leak has been mostly plugged across three of the four major browsers, while the behavioural core of the technique (controlled renders read back losslessly, plus the audio and CPU and language signals around them) still works wherever a browser will draw to a canvas. That is most browsers, most of the time. The browser was always a thin layer. The work from 2017 onward measured exactly how thin, and the number that stuck is that two browsers on one machine were never really two visitors.
Sources & further reading
- Cao, Li, and Wijmans (2017), (Cross-)Browser Fingerprinting via OS and Hardware Level Features — the primary paper; the WebGL rendering tasks, masking, and the 99.24%/83.24% results all come from here.
- NDSS Symposium (2017), (Cross-)Browser Fingerprinting via OS and Hardware Level Features — programme entry — the official abstract and conference record.
- Song Li (2017), cross_browser (source and demo) — the open-source implementation behind uniquemachine.org.
- Laor, Mehanna, Durey, Laperdrix, Maurice, Oren, Rouvoy, Rudametkin, and Yarom (2022), DRAWNAPART: A Device Identification Technique based on Remote GPU Fingerprinting — NDSS 2022; identifies individual GPU chips by execution-unit timing, up to 67% longer median tracking.
- Laperdrix, Rudametkin, and Baudry (2016), Beauty and the Beast: Diverting Modern Web Browsers to Build Unique Browser Fingerprints — the AmIUnique study and its 17-attribute single-browser baseline.
- Boda, Földes, Gulyás, and Imre (2012), User Tracking on the Web via Cross-Browser Fingerprinting — the earlier cross-browser method that leaned on IP address.
- Mozilla (2021), Bug 1722113 — Expose sanitized UNMASKED_RENDERER as RENDERER — Firefox’s bucketed renderer string, shipped around Firefox 92.
- MDN (2024), WEBGL_debug_renderer_info extension — the API that exposes UNMASKED_VENDOR_WEBGL and UNMASKED_RENDERER_WEBGL.
- Castle (2025), The role of WebGL renderer in browser fingerprinting — how Chrome, Firefox, and Safari expose the renderer string differently in current versions.
- Englehardt and Narayanan (2016), Online Tracking: A 1-million-site Measurement and Analysis — the large-scale survey that mapped AudioContext and other fingerprinting in the wild.
Further reading
Canvas fingerprinting: how a single toDataURL call identifies a device
Traces how rendering text and shapes to an HTML5 canvas and hashing the toDataURL output yields a stable per-device value, the GPU, driver and font causes behind the variation, the 2012 origin, and how much entropy it really carries.
·22 min readWebGL fingerprinting: the renderer string, precision, and shader quirks
A primary-source reference on WebGL fingerprinting: the UNMASKED_RENDERER and UNMASKED_VENDOR strings, supported extensions, shader precision formats, rendered-image hashing, and the browser mitigations that bucket or hide them.
·24 min readAudioContext fingerprinting: the OscillatorNode signature explained
Traces how rendering an oscillator through OfflineAudioContext and a DynamicsCompressor produces a stable per-device float, the floating-point and FFT causes behind the variation, the 2016 origin, and how much entropy it really carries.
·18 min read