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 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.

Every profile is drawn from one real computer, not from separate listsIn Mosaic: Machines that exist. GPU, screen, CPU and fonts from one real device. Free holds 3 profiles. Pro is $49 a month for any number.

How it works: Machines that exist

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.

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.

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++ 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 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.