one flat fill reads back as exactly one colour
Check id canvas-flat-fill-uniform
What an ordinary browser yields
Each flat fill reads back as exactly one distinct colour, and that colour is the one that was requested.
What a detector infers
This is the area form of the 1×1 canvas probe, and it exists because that probe is easy to pass by accident. A single black pixel is a weak sample in three separate ways. Black sits at a clipping endpoint, so noise that subtracts is absorbed into zero and never appears. One pixel misses entirely any perturbation applied to a small SUBSET of pixels. And re-reading does not rescue it, because a farble keyed to pixel position and session rather than to the call returns exactly the same bytes on the second read — the obvious 'read it twice and compare' test comes back clean too. A flat fill removes all three escapes at once. One fillRect of one opaque colour has exactly one correct answer at every pixel: no antialiasing, no font, no gradient, no reference image, nothing involved but the browser's own fill instruction compared against what it handed back. Two mid-range colours are used rather than black so that noise has room to move in both directions without clipping. Measured on a shipping stealth browser: filling 4096 pixels with one colour produced five to seven distinct colours, with roughly eight pixels displaced by one unit per channel — and on the same launches, the 1×1 black probe read back pure black every time. That is the whole argument for this row: the browser was provably perturbing canvas output while the older check reported a clean readback, because roughly two pixels in a thousand were touched and the odds of a single sample landing on one are correspondingly small. Reading the same canvas twice gave byte-identical results, and a second canvas drawn identically matched as well, confirming the noise is a deterministic function of position rather than per-call randomness.
How to resolve it
A single fillRect cannot produce more than one colour, so any additional colours were introduced between the fill and the readback. Note this is not scored for Brave, which ships canvas farbling as a stock privacy feature — the same treatment the 1×1 probe gives it. The general point for anyone building such a feature is that sparsity and determinism, which are what make the noise unobtrusive, are also what make it survive re-reads and single-pixel probes while remaining trivially visible to an area probe over a known colour.
Read in the wild by
CloakBrowser (measured)
A flat fill of one opaque colour over 4096 pixels came back as five to seven distinct colours, on every launch measured, with about eight pixels displaced by a single unit per channel. The 1×1 black probe the older check uses read back pure black on all of those same launches, so this browser was perturbing canvas output while passing the check meant to detect exactly that — and a size sweep shows that is structural rather than unlucky. The number of bytes altered is roughly CONSTANT at ten to twenty per read regardless of how large the canvas is, and canvases of 4×4 or smaller are never touched at all: 200 fresh 1×1 canvases produced 200 clean reads. So the older probe cannot catch this browser, at any hit rate, and the area is not merely a better sample but the only sample that works. The perturbation is deterministic within a session — re-reading the canvas and drawing a second identical canvas both produced byte-identical data — and the session key changes per launch. It also leaves the alpha channel alone: every pixel read back fully opaque, including under a context created with alpha:false, whose contract it therefore honours.
rgb(255,102,0) over 64×64 read back as 7, 6 and 7 distinct colours on three launches (dominant colour 4088/4089/4086 px, strays such as 254,103,0 and 255,102,1); rgb(128,128,128) likewise 7/6/6. Size sweep, deviating bytes per read: 1×1 0, 2×2 0, 4×4 0, then 8×8 14-16 of 256, 64×64 11-15 of 16384, 256×256 15-17 of 262144 — a fixed count, not a fixed rate. 1×1 black probe returned [0,0,0,255] on 3/3 audit runs and on 200/200 repeated fresh canvases. alpha != 255 on 0 pixels in both a default and an alpha:false context. Re-read diff 0, second-canvas diff 0. Stock Chrome and Clearcote on the same machine: exactly 1 distinct colour on every fill, at every size.
CloakBrowser (measured)Every attribution traces to a published artifact. See the sources and their limits.
Nearby checks in Render & GPU
- 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 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… - a canvas reads back the same through toDataURL and getImageData
canvas-todataurl-vs-getimagedatatoDataURL and getImageData are two exits from one canvas, and PNG is lossless, so decoding the data URL back into pixels has to reproduce… - one WebGL clear reads back as exactly one colour
webgl-readback-flat-uniformA single glClear writes one colour into every pixel of the framebuffer, so reading that framebuffer back must yield exactly one colour. - a WebGL framebuffer reads back the same through readPixels and toDataURL
webgl-readpixels-vs-todataurlreadPixels and toDataURL both read the drawing buffer of one WebGL canvas, and PNG is lossless, so decoding the data URL has to reproduce…
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 canvas-flat-fill-uniform belongs to.