WebGL exposes a full parameter table + UNMASKED vendor/renderer
Check id webgl-param-table
What an ordinary browser yields
Non-empty unmasked vendor and renderer, plus 12 or more parameters returning values.
What a detector infers
Beyond the two headline strings, a WebGL context answers getParameter for dozens of driver-specific limits. The audit queries 21 of them — MAX_TEXTURE_SIZE, MAX_VIEWPORT_DIMS, MAX_VERTEX_UNIFORM_VECTORS, ALIASED_LINE_WIDTH_RANGE, the RED/GREEN/BLUE/ALPHA/DEPTH/STENCIL bit depths, MAX_TEXTURE_MAX_ANISOTROPY_EXT and others — hashes the collected values, and passes when at least 12 of them returned a value and the context is not masked: WEBGL_debug_renderer_info must be present, UNMASKED_VENDOR and UNMASKED_RENDERER must both be non-empty, and the renderer must not read "Masked" or "Not supported". Only a missing WebGL context reports N/A — a stripped debug extension is a fail, not an abstention. Note the scope: it verifies the table is present and unmasked, not that the limits match the named GPU. That second step is the detector's: they hash the whole table, so a rewritten renderer string can be checked against limits that belong to a different GPU — which is why exposing an honest table only helps if the strings describe it.
How to resolve it
Expose a real GPU's parameter table by giving the browser a genuine GPU-backed context (canvas bridge). Don't strip WEBGL_debug_renderer_info or blank out vendor/renderer — the absence is itself distinguishing. If you do set the strings, set them to a GPU whose limits actually match the table you are exposing.
Read in the wild by
PerimeterX / HUMAN
Collects the full WebGL driver-limit table — the aliased line-width and point-size ranges, the alpha, blue and green bit depths, depth and stencil bits, the MAX_* limits and MAX_TEXTURE_MAX_ANISOTROPY_EXT — and appends a getShaderPrecisionFormat reading (precision, rangeMin, rangeMax) for each shader and precision pair, sending the table both as a raw array and as a hash. getSupportedExtensions() is collected and hashed as its own separate field.
stample/ifood/source/main.min.js (identifiers hQ/Ak; the same logic appears in stample/walmart and stample/totalwine under different minified names): `n.webglParameters=[];var h=n.webglParameters;if(h[hQ(234)](c(Ak(t,t.ALIASED_LINE_WIDTH_RANGE))),h.push(c(Ak(t,t.ALIASED_POINT_SIZE_RANGE))),h[hQ(234)](Ak(t,t.ALPHA_BITS)),h.push(t.getContextAttributes()[hQ(493)]?"yes":"no"),h.push(Ak(t,t.BLUE_BITS)),h.push(Ak(t,t.DEPTH_BITS)),h.push(Ak(t,t.GREEN_BITS)),h.push(function(t){…e=t.getExtension("EXT_texture_filter_anisotropic")…t.getParameter(e.MAX_TEXTURE_MAX_ANISOTROPY_EXT)…}(t)),…h.push(Ak(t,t.MAX_CUBE_MAP_TEXTURE_SIZE)),h.push(Ak(t,t.MAX_FRAGMENT_UNIFORM_VECTORS)),…h.push(c(Ak(t,t.MAX_VIEWPORT_DIMS))),h.push(Ak(t,t.STENCIL_BITS))` — note RED_BITS is absent (grep -c RED_BITS = 0). Precision: `var l=t[hQ(497)](t[u],t[s]);h.push(l.precision,l[hQ(500)],l[hQ(501)])`. Extensions: `n.extensions=t[hQ(490)]()||[pd]`. Dispatch pairs each raw value with a hash: `e["PAhJQnpoSHY="]=n.extensions,e["GCQtLl1ELx8="]=iR(n.extensions),e["LVkYU2g9Hmg="]=n.webglParameters,e["dWFAKzABQhs="]=iR(n.webglParameters)`. Decoded payload stample/ifood/sample/1/decoded_payload_2.json confirms: LVkYU2g9Hmg= = ["[1, 1]","[1, 1024]",8,"yes",8,24,8,16,32,16384,1024,16384,16,16384,30,16,16,4096,"[32767, 32767]","no_fp",23,127,127,…] (20 params + 12 precision triples), dWFAKzABQhs= = "dab81cb438e9b1ecd9151a3ba33a82b8", GCQtLl1ELx8= = "96ff435b6ebac2817a4d5bfc475aa8e4", PAhJQnpoSHY= = ["ANGLE_instanced_arrays","EXT_blend_minmax",…]. hQ(490)/hQ(497) semantics confirmed empirically from these payload values. Walmart build independently shows the same raw+hash pairing: `n["bRJWEyh9Xig="]=t.webglParameters,n["GwBgAV5rbDE="]=fJ(t.webglParameters)`.
Kasada
Hashes a full WebGL getParameter table rather than trusting the vendor string alone.
gl_unmasked_vendor, gl_unmasked_renderer, gl_version, gl_max_uniform_buffer_bindings, gl_max_fragment_uniform_vectors, gl_aliased_line_width_range …
ips.js 427-probe teardown (emro.cat, Apr 2026)
Akamai
Lists the GPU renderer and vendor via WEBGL_debug_renderer_info as hardware signals.
signal_categories.md - 2. Hardware - WebGL renderer / WebGL vendor
Edioff/akamai-analysis — signal_categories.mdEvery attribution traces to a published artifact. See the sources and their limits.
Nearby checks in Render & GPU
- WebGL renderer (software vs hardware)
webgl-not-softwareThe audit reads UNMASKED_RENDERER through the WEBGL_debug_renderer_info extension and matches it against SwiftShader, llvmpipe and Software. - WebGPU vendor coheres with WebGL vendor
webgl-webgpu-vendorTwo independent graphics stacks in the same browser are backed by the same physical adapter: this check lowercases the WebGL UNMASKED_VENDOR… - WebGL vendor/renderer identical in a Worker
webgl-worker-vs-mainThe audit reads UNMASKED_VENDOR and UNMASKED_RENDERER on the main thread, then spawns a dedicated Worker that builds its own WebGL context… - masked WebGL VENDOR matches the engine
webgl-engine-vendorThis check reads the plain gl.getParameter(gl.VENDOR) — the masked value, not the debug extension's unmasked GPU string. - WebGL1 and WebGL2 report the same UNMASKED GPU
webgl1-webgl2-coherenceThe check creates a WebGL1 context and a WebGL2 context and reads UNMASKED_VENDOR_WEBGL / UNMASKED_RENDERER_WEBGL from each through the… - canvas readback is not noise-injected (1×1 probe)
canvas-tamperThe probe fills a single pixel with pure black on a 1x1 canvas and reads it straight back with getImageData. - one flat fill reads back as exactly one colour
canvas-flat-fill-uniformThis is the area form of the 1×1 canvas probe, and it exists because that probe is easy to pass by accident. - one pixel reads the same through a full read and a windowed read
canvas-subrect-consistencyThe same pixels, asked for twice through different windows. A full-canvas read and an inner 16×16 read overlap on 256 pixels, and those are…
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-param-table belongs to.