What WebGPU exposes that WebGL does not
WebGPU was designed for heavy compute work, so it reveals far more structured hardware detail than WebGL. A fingerprinting 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 set of numbers is tightly tied to the GPU model and its driver. - Compute output - run a known compute shader over known inputs, read the result back, and hash it. The tiny floating-point rounding differences in the shader cores vary between vendors and architectures.
The combination carries more entropy (more identifying power) than WebGL, because the limits object alone encodes the GPU tier as about 40 correlated numbers rather than one string.
Why headless servers fail WebGPU
On a server with no GPU, navigator.gpu.requestAdapter() resolves to null - there is simply no graphics card to describe. Real desktop Chrome on consumer hardware almost always returns a working adapter, so a null adapter on a request that claims to be a normal desktop user is a strong anomaly. Forcing software rendering instead (Chrome's Dawn/SwiftShader WebGPU backend, which imitates a GPU in plain code) returns an adapter whose vendor is reported as a software fallback and whose limits match no real GPU - an equally clear tell.
The fixes mirror the ones for 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 one coherent real-device profile. Spoofing adapter.limits from JavaScript is detectable, because the patched getter functions fail Function.toString() inspection (a check that prints a function's source to see if it has been tampered with).
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 contradicts itself and gets blocked. A request that spoofs one API but leaves the other at its headless default is worse off 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) pairs per session are the only reliable defence.
