What WebGL exposes
The browser API exposes four classes of identifying data:
- Renderer string — 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 exact set varies by GPU model and driver version. - Parameter values — max texture size (4K/8K/16K), max viewport dimensions, max vertex attributes, fragment shader precision. Each varies by hardware tier.
- Rendered output — draw a known shape with a known shader and hash the pixels read back from the canvas. Different GPUs produce subtly different floating-point rounding, anti-aliasing, and gradient interpolation.
Why headless browsers fail WebGL the hardest
Headless Chrome without a GPU falls back to SwiftShader, Google's software rasterizer. The renderer string is "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 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 real GPU passed through, (2) spoof WEBGL_debug_renderer_info at the CDP level with a believable renderer string, or (3) use a tool like Camoufox or CloakBrowser that patches the renderer at the C++ level.
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. 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 with 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. This is more expensive than spoofing individual values, which is why DIY hardening of WebGL almost always trips at least one cross-check.
