What WebGPU exposes that WebGL does not
WebGPU was designed for compute, so it surfaces far more structured hardware detail than WebGL. A fingerprint probe collects three layers:
- Adapter info -
adapter.inforeturnsvendor("nvidia", "apple", "intel"),architecture("turing", "apple-m", "gen-12lp"), and sometimesdeviceanddescriptionstrings. - Limits -
adapter.limitsis an object of roughly 40 numeric ceilings:maxTextureDimension2D,maxBufferSize,maxComputeWorkgroupSizeX,maxStorageBufferBindingSize, and more. The exact tuple is tightly bound to the GPU model and driver. - Compute output - dispatch a known compute shader over known inputs, read back the buffer, and hash it. Floating-point rounding in the shader cores differs between vendors and architectures.
The combination is higher-entropy than WebGL because the limits object alone encodes the GPU tier as ~40 correlated numbers rather than one string.
Why headless servers fail WebGPU
On a server with no GPU, navigator.gpu.requestAdapter() resolves to null. Real desktop Chrome on consumer hardware almost always returns a working adapter, so a null adapter on a request claiming to be a normal desktop user is a strong anomaly. Forcing software rendering (Chrome's Dawn/SwiftShader WebGPU backend) returns an adapter whose vendor is reported as a software fallback and whose limits match no real GPU - an equally clear tell.
The mitigations mirror WebGL: pass a real GPU through to the browser (xvfb plus a physical or virtual GPU), or patch the adapter at the engine level so the info, limits, and compute output all come from a coherent real-device profile. JavaScript-level spoofing of adapter.limits is detectable because the patched getters fail Function.toString() inspection.
The WebGL/WebGPU coherence check
The decisive technique is cross-checking WebGPU against WebGL. Both APIs describe the same physical GPU, so their stories must agree. If WebGL reports "Apple GPU" but the WebGPU adapter vendor is "nvidia", or WebGL reports an RTX 4070 while the WebGPU maxComputeWorkgroupStorageSize matches an integrated Intel chip, the request is incoherent and gets blocked. A request that spoofs one API but leaves the other at its headless default is worse than spoofing neither.
This is the same lesson as everywhere else in fingerprinting: a believable identity is a coherent cluster of values harvested from one real machine, not a pile of individually-plausible spoofs. Engine-level tools (Camoufox, Chromium forks) that serve matched (WebGL, WebGPU) tuples per session are the only reliable defence.
