Pixelscan's WebGL fingerprint has two parts: a hash of one rendered triangle, and a record of about sixty driver fields sitting beside the renderer string. We decoded its collector and then measured it. A chip that never existed failed exactly like a real one, so the check is whether the string agrees with the record, not whether the chip is on a list.
What the Pixelscan WebGL fingerprint collects
Pixelscan's collector is pixelscan.net/assets/fptc.min.js. It is obfuscated only with \xNN escapes, so four lines of Python turn it back into readable code, and it names what it gathers:
audio, canvasHash, fonts, hardwareConcurrency, language, navigatorPlatform, screenResolution, secCh, timezone, ua, webDriver, webglHash, webglMeta
Two of those are WebGL. webglHash compiles a fixed vertex shader and a fixed fragment shader, draws one triangle from three hard-coded vertices, and MD5s the framebuffer. webglMeta is the claim: unMaskedVendor and unMaskedRenderer, and next to them about sixty capability fields. Every MAX_* limit is there, with the shader precision formats, the bit depths, anisotropy, the extension list and the WebGL2 function list.
One cross-check is visible in the script itself. On Win32 or Win64 it looks for D3D11 or D3D9 in the renderer and compares that against MAX_VERTEX_UNIFORM_VECTORS. Everything else is decided on Pixelscan's server, so we can say what it reads and not exactly how it weighs each field.
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.
Mosaic is a desktop app
Email yourself the link and open it on your computer.
How it works: Machines that exist
The theory we wrote down, and why it was wrong
The obvious reading is that the hash is the ground truth and the string is the claim: the GPU draws the triangle, the pixels say which GPU it really is, and a chip you don't own is arithmetic. We wrote exactly that into our notes. Three measurements took it apart.
First, we noise the WebGL framebuffer per profile (the colour-keyed stamp that makes readPixels and toDataURL agree is in readPixels and toDataURL read a canvas from opposite ends). With the noise on, the hash matches no real device at all. The host's own chip still passed and every other chip still failed. The hash is not what decides it.
Second, we claimed Apple M9 Ultra, which has never been made. It failed exactly as Apple M2 Pro did. If Pixelscan were looking chips up in a table of known devices, an unknown string would have had nothing to contradict.
Third, the masked pair is not leaking the real chip. getParameter(RENDERER) returns WebKit WebGL and VENDOR returns WebKit, byte for byte what stock Chrome returns. (The unmasked constants have their own trap: they answer null until the page asks for the debug extension, covered in UNMASKED_RENDERER is null until a page asks for it.)
What is left is the capability record. We diffed 56 of its fields against a stock Google Chrome on the same Mac, and they came back identical. That is correct behaviour, and it is also why the claim fails: Pixelscan reads sixty honest driver fields and one string that disagrees with them.
The measurements, one profile patched in place
Every row below comes from one profile on an M4 Max host, patched in place over several sweeps, with only the renderer string moving and the host's own chip run as the control in each sweep. This was a stock-browser build, where a claimed renderer sits on top of the record the real driver wrote.
| Claimed renderer | Pixelscan |
|---|---|
| Apple M4 Max (the host's chip) | pass, every round |
| Apple M4 Pro | fail |
| Apple M4 | fail |
| Apple M3 Max | fail |
| Apple M2 Pro | fail |
| Apple M1 | fail |
| Apple M9 Ultra (never existed) | fail |
It is the exact chip, not the family and not the generation. An M4 Pro on an M4 Max is as wrong as an M1. The thread count is a separate field and was free: M4 Max at 12 threads passed beside M4 Max at 16, and M2 Pro failed at both.
The cost was one headline. The same profile claiming M2 Pro still scored Trustworthy on IPHey and 90% on BrowserScan, the same as it did claiming its own chip. Only Pixelscan's verdict moved.
What it takes to claim another GPU
The rule we already worked to says only change a surface if every route to it can be made to agree. For a GPU on a stock browser that means the whole record, not the string: the limits, the precision formats and the extension list of a real target device, kept coherent with each other. We don't have per-GPU capability data for that, and we will not write it from a hand-made table, because the record is fifty times more surface to get wrong than a renderer name.
Tesseract Chrome, our own Chromium, changes the renderer inside the engine, in C++, instead of laying a string over a driver's answer. The same sweep on it, one profile patched in place with the host's chip as the control at both ends of two runs, came back differently: M1, M2, M3, M2 Pro, M1 Max and the invented M9 Ultra all passed. Two freshly drawn profiles, an M3 Pro and an M2 on the M4 Max host, passed Pixelscan, IPHey and BrowserScan. Which values the engine draws together from real hardware is on real fingerprints, and the font half of Pixelscan's consistency check, which no renderer string touches, is in Pixelscan's masking verdict, bisected to one surface.


