What WebGL exposes
The browser API exposes four classes of identifying data:
- Renderer string — the GPU's name, read via
gl.getParameter(gl.RENDERER)with theWEBGL_debug_renderer_infoextension. Returns strings like"ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0, D3D11)"on Windows or"Apple GPU"on macOS. - Extension list — about 70 named extensions (
EXT_color_buffer_float,OES_texture_float_linear, etc.), the optional features your GPU and driver support. The exact set varies by GPU model and driver version. - Parameter values — limits the hardware reports, such as max texture size (4K/8K/16K), max viewport dimensions, max vertex attributes, and fragment shader precision (how accurate the GPU's per-pixel maths is). Each varies by hardware tier.
- Rendered output — draw a known shape with a known shader (a small GPU program) and hash the pixels read back from the canvas into a short ID. Different GPUs produce subtly different floating-point rounding, anti-aliasing (edge smoothing), and gradient interpolation, so the ID is steady but machine-specific.
Why headless browsers fail WebGL the hardest
A headless browser runs with no visible window, the usual setup for automation. Headless Chrome without a GPU falls back to SwiftShader, Google's software rasterizer - a stand-in that draws graphics on the CPU instead of a real GPU. The renderer string is then "Google Inc. SwiftShader" or "Google SwiftShader" — anti-bot vendors block this string unconditionally because no real desktop user has SwiftShader as their primary GPU. The fallback chain is even worse on Linux: llvmpipe (Mesa's software rasterizer) is an instant tell.
Running headless Chrome with --use-gl=angle --use-angle=swiftshader-webgl still produces a SwiftShader renderer. The mitigations are: (1) run with xvfb (a virtual display) plus a real GPU passed through, (2) spoof WEBGL_debug_renderer_info at the CDP level (Chrome's remote-control protocol) with a believable renderer string, or (3) use a tool like Camoufox or CloakBrowser that patches the renderer at the C++ level (inside the browser engine itself).
The renderer-platform coherence trap
Spoofing the renderer string alone is insufficient. Anti-bot vendors cross-check the claimed renderer against the platform (navigator.platform, navigator.userAgent) and the GPU's expected extension list - in other words, do all the clues agree with each other? A request claiming Windows + Chrome 131 with renderer string "Apple GPU", or macOS with an NVIDIA RTX renderer, gets blocked. The renderer extension set must also match the claimed hardware — a budget integrated GPU advertising the extensions only found on enthusiast cards is a clear tell.
Camoufox's approach is to maintain a database of real (platform, GPU, renderer, extensions, parameters) tuples harvested from real users, and serve a coherent one per session, so every value matches. This is more expensive than spoofing individual values, which is why DIY hardening of WebGL almost always trips at least one cross-check.
