Skip to content
All fingerprint checksRender & GPU

the GL limits the driver reports are limits the driver will actually honour

Check id webgl-limits-vs-driver

What an ordinary browser yields

A program using exactly MAX_VERTEX_UNIFORM_VECTORS uniform vectors links, one using a single vector more does not, and any limit the driver names in its own error matches the one getParameter reported. The driver's per-format sample list reaches MAX_SAMPLES. On legacy GPU classes — generic Intel HD Graphics without a model number (Sandy Bridge era, and Firefox resistFingerprinting's generic renderer) and the Radeon HD 3000–5000 series — per-format support legitimately diverges from the cross-format maxima, so a disagreement there is reported as N/A, not a contradiction.

What a detector infers

Every other WebGL check on this page reads what getParameter SAYS. This one asks the driver to HONOUR it. getParameter is a lookup a page or a patched browser build can rewrite; linking a program is work the GPU either does or refuses, and allocating a multisampled renderbuffer is a capability the backend either has or lacks. So the second source is the hardware itself. The uniform probe builds a program using exactly the advertised number of vertex uniform vectors and requires it to LINK, then builds one using a single vector more and requires that to FAIL — the limit must bind in both directions. When it does not, ANGLE usually names the number it actually enforced in its own error text, so the driver states the true value out loud and the contradiction fits in one line. Three details are load-bearing and were each wrong in the first version of this check. It probes at LINK time, not compile time, because the GLSL compiler happily accepts an array far past the advertised limit — measured at 8190 against a claimed 4095 on a stock Chrome — since the limit is enforced by the linker. The shader CONSUMES every element of the array, because a shader that reads only the first element lets the optimiser strip the rest and any size compiles, which made the original probe vacuous: it could not fail, and it reported that a browser honoured its advertised limits while that browser was provably advertising one its driver refused. And it checks both directions rather than only the shortfall, because the interesting case is a build that advertises a limit the real hardware will not honour. The samples probe is the same idea through a second door: MAX_SAMPLES against getInternalformatParameter, which goes to the driver at call time and returns only the sample counts the backend really supports. Note what makes this durable: there is no table of real limits per GPU, no list of models, nothing to maintain. The only question is whether this machine can do the thing it just claimed it could, and the machine answers for itself. A third arm probes the WebGL2 uniform-BLOCK limits, and the story of why it exists is worth keeping. After the vertex-vector arm caught an over-claim, the obvious follow-up was to sweep every other limit the linker enforces: fragment uniforms, varyings, attribs, texture units, plus the allocation-enforced set of texture, renderbuffer, 3D-texture and array-layer sizes. Ten limits, every one exact on both browsers, and the conclusion drawn was that the spoof was narrow and there was nothing further to find. That conclusion was wrong, because the sweep had covered WebGL1 limits only. The WebGL2 uniform-block limits are over-claimed by a different mechanism and a different margin: not an off-by-one but fourteen advertised against twelve enforced, on both the vertex and the fragment stage. Stock Chrome 150 on the reference machine advertises twelve and links exactly twelve; the patched build advertises fourteen, refuses thirteen, and names the real ceiling in its own link error: GL_MAX_VERTEX_UNIFORM_BUFFERS (12). The lesson generalises past this one row: a negative result across a family of limits is evidence about the limits actually tested, and nothing at all about the ones that were never in the sweep. A fourth arm then asks the driver nothing at all, and it is the cheapest and most durable of the four. The GL ES 3.0 specification does not leave these limits independent of one another: the combined uniform-component total for a stage is its default block plus every uniform block that stage can bind, which makes MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS, MAX_VERTEX_UNIFORM_COMPONENTS, MAX_VERTEX_UNIFORM_BLOCKS and MAX_UNIFORM_BLOCK_SIZE four numbers bound by one equation. A build that rewrites one of them and not the others breaks arithmetic rather than merely reporting an unusual value, and no hardware is needed to notice. Measured on one machine, one GPU, two browsers: stock reports twelve vertex blocks and a combined total of 212988, and 16380 plus 12 times 16384 is exactly 212988. The patched build reports fourteen blocks on each stage while its combined totals remain 212988 and 200704, byte-identical to stock. The per-stage counts were raised and the combined totals, which the driver derived from the real counts, were left behind, so they still imply twelve. That same build reports MAX_COMBINED_UNIFORM_BLOCKS 24 against fourteen plus fourteen, a combined limit smaller than the sum of the two things it combines. The comparisons are written as at-least rather than exact on purpose: a driver reporting more combined storage than the parts require has not lied about anything, whereas one reporting less has claimed a per-stage capability its own combined budget cannot fund, and only that direction is a contradiction. Because it needs no GPU work, this arm keeps answering on machines where the linker probe cannot run at all. Two unrelated stealth builds have now been measured failing the linker arm on the same limit and by the same margin: each advertises 4096 vertex uniform vectors on a machine whose driver enforces 4095, having picked a round number for a persona GPU table that the hardware underneath will not honour. That is worth stating precisely rather than as a boast, because against one of them the detection is a RATE and not a certainty. That build draws a fresh random persona on every launch, and roughly half over-claim. The split is not random but it is not about one vendor either: across sixteen launches, every persona claiming an NVIDIA part advertised 4095 and passed, while every persona claiming anything else — Intel parts and an AMD one — advertised 4096 and failed. The correlation held perfectly in both directions over those sixteen draws. It is host-dependent as well as persona-dependent: the reference machine's driver enforces 4095, so the non-NVIDIA personas over-claim against it, whereas on a machine whose driver really did allow 4096 those same personas would pass and the NVIDIA ones would be under-claiming, which this row reports as a note rather than a finding. So a quiet verdict here means this browser, this persona and this GPU agreed on this machine; it does not mean the build never over-claims.

How to resolve it

These limits are not metadata, they are promises the GPU has to keep. A build that reports a GPU profile's limits from the JavaScript binding layer leaves the linker enforcing the real hardware's numbers underneath, so the advertised value and the enforced value part company — and the driver prints the real one in its error. Clamp any reported limit to what the actual driver enforces, so the claim can never exceed the capability, or present a GPU whose driver genuinely has those limits.

What is WebGL fingerprinting?

Read in the wild by

Kasada

Reads the GL limit constants directly and hashes the whole parameter table — which is exactly why the limits are worth verifying against the driver rather than trusting as reported.

gl_max_uniform_buffer_bindings, gl_max_fragment_uniform_vectors, gl_max_combined_uniform_blocks, gl_max_combined_vertex_uniform_components, gl_aliased_line_width_range

"How I Broke the Anti-Bot Behind Nike, Kick, and Twitch" — emro.cat, April 2026

Every attribution traces to a published artifact. See the sources and their limits.

Nearby checks in Render & GPU

See all 32 checks in Render & GPU
Who builds this test

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-limits-vs-driver belongs to.