Skip to content
All fingerprint checksRender & GPU

WebGL vendor/renderer identical in a Worker

Check id webgl-worker-vs-main

What an ordinary browser yields

Worker and main thread name the identical vendor and identical renderer.

What a detector infers

The audit reads UNMASKED_VENDOR and UNMASKED_RENDERER on the main thread, then spawns a dedicated Worker that builds its own WebGL context on an OffscreenCanvas and reads the same two strings, requiring exact equality of both. A Worker is a separate JavaScript realm with its own globals: a patch applied to the page's WebGLRenderingContext.prototype.getParameter never executes there, so the Worker reports the real GPU while the main thread reports the fictional one. A detector treats that as a high-confidence signal because no physical machine presents two different GPUs to two scopes of the same page — the disagreement can only come from a modification that covers one scope. N/A if the Worker probe errors or times out (4s), or if either scope has no debug-renderer-info to read.

How to resolve it

Move GPU identity below JavaScript — an engine-level control or a canvas bridge — so every realm reads the same value from one source of truth. Chasing this by also injecting into every Worker is fragile: each new realm (Workers, nested Workers, iframes) is another place the patch must reach.

Why must a worker report the same values as the page?

Read in the wild by

CreepJS

CreepJS, an open-source fingerprinting research demo rather than a production detector, compares the main-thread UNMASKED_RENDERER_WEBGL string against the renderer a worker scope reports and flags any inequality as `hasBadWebGL`, one of the booleans in its `stealth` group that feed its stealth rating.

src/headless/index.ts — `hasBadWebGL: (() => { const { UNMASKED_RENDERER_WEBGL: gpu } = webgl?.parameters || {}; const { webglRenderer: workerGPU } = workerScope || {}; return (gpu && workerGPU && (gpu !== workerGPU)) })(),` — declared inside the `stealth` group of `getHeadlessFeatures`, which is scored as `(stealthKeys.filter((key) => stealth[key]).length / stealthKeys.length) * 100`.

Every attribution traces to a published artifact. See the sources and their limits.

Nearby checks in Render & GPU

See all 32 checks in Render & GPU
Who builds this test

Clearcote is a browser built for fingerprint coherence

It is a Chromium fork, maintained by the same people who wrote this reference. It ships as a compiled browser rather than as a stealth script injected into someone else's — which is a description of how it is built, and is not an argument about how it behaves on this check.

This audit takes no position on how Clearcote scores on Render & GPU checks, on this one, or anywhere else. It has no baseline corpus of other people's fingerprints to rank you against and no vendor scoreboard — nearly every check is self-referential, asking one browser the same question through two independent APIs and reporting whether both answers can be true at once. It runs identically on any browser, including ours. Run it on yours and read the result yourself.

See the other checks in Render & GPU — the family webgl-worker-vs-main belongs to.