# Why your WebGL fingerprint is the same in every browser

> A WebGL fingerprint is the same in every browser profile on one machine because the GPU draws it. Ours was too, until per-profile noise moved the hash.

Source: https://mosaicbrowser.com/blog/webgl-hash-same-every-profile

## Every profile on one Mac drew the same WebGL image

A WebGL fingerprint is the same in every browser profile on one machine because the GPU draws it. Ours was too, until per-profile noise moved the hash.

![f5337a44: One scene, one Mac, one hash for every profile](https://mosaicbrowser.com/covers/webgl-hash-same-every-profile.webp)

A WebGL fingerprint is the same in every browser profile on one machine because it is the machine's, not the browser's. A page draws a fixed 3D scene, reads the pixels back and hashes them, and those pixels come out of your GPU, its driver and the operating system's graphics stack. A new Chrome profile, an incognito window or a VPN changes none of the three. On our own Mac, every profile drew the same image, down to the hash, until we gave each one its own noise.

## Why the WebGL fingerprint is the same in every browser

A WebGL scene is a list of triangles, colours and shader code. Turning that into pixels is the GPU's job, and every step leaves a mark: how the hardware rounds a colour, how it smooths an edge, how the driver compiles a shader, which backend translates the calls. In Chrome on a Mac that backend is ANGLE over Metal, the same word you see in the renderer string, which [what ANGLE means in your renderer string](https://mosaicbrowser.com/blog/angle-in-webgl-renderer) takes apart.

Two different machines draw the same scene a few pixels differently. One machine draws it identically every time, because nothing in the chain moved. That is what makes the hash useful to a fingerprinting script: it is stable, it costs one draw call, and it names a kind of machine.

It also means the browser's own settings are beside the point. A Chrome profile separates cookies, history and extensions. Incognito separates storage. A VPN or proxy changes the address. The scene goes through the same GPU in all of them, so the hash is the same in all of them.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Machines that exist](https://mosaicbrowser.com/features/real-fingerprints)

## What we measured on one Mac

For a long time we did not noise WebGL at all, so the image was identical for every profile on the machine. On Pixelscan's own WebGL scene, an untouched stock browser on the same Mac hashed to `76edd36b`, and nothing in a profile gave it a reason to differ.

That is real shared entropy on a surface fingerprinters weight heavily. Two profiles with different screens, fonts, timezones, cookies and exits still drew one picture, and any site reading it could put them on the same computer.

It was worse than unfinished, because the editor said otherwise. `webglImage` was a per-profile setting the profile editor offered and the launcher passed along to the browser. The script that should have acted on it read it nowhere. It looked like a working control for as long as it existed, which is the kind of fault we now hunt for on purpose, like the proxy setting in [saved, and ignored by three launches](https://mosaicbrowser.com/blog/setting-saved-launch-ignored).

## Why it was skipped, and what fixed it

We had skipped WebGL noise because the first attempt broke something else. It chose which pixels to change by their coordinates, and WebGL has two ways to get the picture out: `readPixels` counts rows from the bottom, `toDataURL` from the top. A position-keyed stamp landed on different pixels through the two routes, they disagreed, and Pixelscan called the profile masked. That story, row by row, is in [readPixels and toDataURL read a canvas from opposite ends](https://mosaicbrowser.com/blog/webgl-readpixels-vs-todataurl).

The 2D canvas had had the same bug and had been fixed by keying the stamp on a pixel's colour, which is the same whichever end you count from. Nobody had carried that across. Once we did, the numbers were:

```
readPixels vs decoded toDataURL   4096 of 4096 pixels agree
repeat reads, each route          stable
Pixelscan scene hash              76edd36b (stock) -> f5337a44
geometry                          byte-identical to stock

```

The hash moved and the shape did not. A page reading the image twice gets the same answer twice, through either route.

## How Tesseract Chrome does it now

That first fix was an injected script on a stock browser. Tesseract Chrome, Mosaic's own Chromium, does it in C++ instead, because a value changed in page JavaScript is one a worker or another frame can read unchanged ([why we change fingerprints in C++](https://mosaicbrowser.com/blog/cpp-not-javascript) has the reasons). The engine's `readPixels` patch calls the same colour-keyed function the 2D canvas readbacks go through, so there is one rule and no second copy of it to drift.

It stamps one distinct colour in 32 and leaves the other 31 exactly as drawn. It stamps rather than flips, so a second read finds the change already there instead of a new one. Each profile has its own seed, so each profile's image hash is its own, while everything about the GPU stays true: the renderer, the limits and the precision formats are drawn together for one real machine, as [real fingerprints](https://mosaicbrowser.com/features/real-fingerprints) describes.

## Checking your own setup

1. Open the same WebGL test page in two profiles. BrowserLeaks' WebGL page prints an image hash.
2. Compare the hashes. The same value in both means a site can join them on that surface.
3. If you add noise, read one frame through `readPixels` and through `toDataURL`, flip one, and diff. A real browser gives zero differing pixels.

A shared hash is the honest result for two browsers on one computer. It just means WebGL is not separating them, and something else has to.

## Questions

### Why is my WebGL fingerprint the same in every browser profile?

Because the image is drawn by your GPU, its driver and the operating system's graphics stack, and every profile on the machine uses the same ones. Cookies, history and the profile itself play no part in the pixels.

### Does incognito or a VPN change the WebGL fingerprint?

No. Incognito keeps the same browser and the same GPU, and a VPN only changes the address. The scene comes out of the same hardware, so it hashes the same.

### How can I check whether two profiles share a WebGL image?

Open the same WebGL test page in both, for example BrowserLeaks' WebGL page, and compare the image hash. If it is the same in both, a site can tell they run on one machine from that surface alone.

### Does Mosaic give each profile a different WebGL image?

Yes. Tesseract Chrome stamps a sparse, colour-keyed change into what readPixels and the canvas readbacks return, in C++, so each profile's image hash differs while the geometry stays as drawn.

## Related

[readPixels and toDataURL read a canvas from opposite ends](https://mosaicbrowser.com/blog/webgl-readpixels-vs-todataurl): readPixels starts at the bottom row and toDataURL at the top, so position-keyed WebGL noise disagreed with itself. Keyed on colour, 4096 of 4096 pixels agree.

[What ANGLE means in your WebGL renderer string](https://mosaicbrowser.com/blog/angle-in-webgl-renderer): ANGLE is the layer Chrome uses to turn WebGL into Direct3D, Metal or OpenGL. The backend it names in the renderer string tells a site your operating system.

[What a canvas hash actually tells a site about your machine](https://mosaicbrowser.com/blog/what-a-canvas-hash-reveals): A canvas fingerprint reveals your rendering stack: GPU, driver, fonts, the OS's text rasteriser and antialiasing. It names a kind of machine, not a person.

[Machines that exist: fingerprints drawn from real hardware](https://mosaicbrowser.com/features/real-fingerprints): Mosaic draws each profile's GPU, screen, CPU, memory and fonts from one real device, so the parts of a fingerprint agree with each other and with the host.



[Same fonts, different measurements: per-profile glyph advances](https://mosaicbrowser.com/blog/glyph-advances-per-profile): Font fingerprint randomization can hide fonts or change how wide glyphs measure. Hiding read as masking; per-profile glyph advances did not.

[The four-line probe that catches position-keyed canvas noise](https://mosaicbrowser.com/blog/canvas-offset-probe): To detect canvas noise injection, draw one shape twice at two offsets and diff them. A real browser gives 0 bytes; our old noise gave 76.

[The canvas method nobody noised: what a worker test found](https://mosaicbrowser.com/blog/the-canvas-method-nobody-noised): Our canvas noise reached toDataURL and missed toBlob, so a page calling toBlob got a PNG byte-identical to stock Chrome's. A worker test caught it.

## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. Free holds 3 profiles. Pro is $49 a month for any number.

Mosaic is a desktop app

Email yourself the link and open it on your computer.
