one canvas encodes the same pixels through toBlob and toDataURL
toBlob and toDataURL decode to byte-identical pixels. One surface encoded twice is one image, on every engine.
What a detector infers
A canvas has three exits — getImageData, toDataURL and toBlob — and a build that perturbs canvas readback has to perturb all three identically or it contradicts itself. This audit already compares the first two. toBlob is the third, and it is worth asking separately because in a real engine it is a different code path from toDataURL: same surface, same encoder, different entry point.
That is precisely the kind of seam where a patch gets applied in one place and missed in another, and the result is a canvas that hashes two different ways depending on which method a detector happens to call. The inference available to a detector is unusually strong, which is what makes this row worth its cost.
It does not need to know what the correct pixels are, or hold any reference value, or know anything about the machine — it calls two methods on one canvas and observes that the browser gave two answers to one question. No amount of per-site seeding makes that legitimate.
The comparison is done on decoded PIXELS rather than on the encoded bytes on purpose: two PNG encoders can legitimately differ in compression choices while painting identical images, and failing a browser for that would be exactly the false positive this audit takes most seriously.
How to resolve it
Apply canvas perturbation once, at a point every exit shares, rather than separately in each encode path. Per-exit seeding makes toDataURL, toBlob and getImageData drift apart, and that disagreement is self-evident to anyone who calls two of them — it needs no reference image to detect.
Read in the wild by
CloakBrowser issues
A stealth-browser tracker records canvas readback differing between exits on one surface, and the resulting two-hashes-from-one-canvas signal.
CloakHQ/CloakBrowser #464, #294, #437 (canvas exit divergence)
CloakHQ/CloakBrowser#464Clearcote (measured)
Confirmed non-accusatory on a stock browser before shipping: the same 40x20 surface encoded through both exits and decoded back gave byte-identical pixels, so the invariant this row asserts is one an unmodified engine actually holds.
stock desktop Chromium on Windows (ANGLE/D3D11, RTX 3070): 0 of 3200 channel bytes differed between toBlob and toDataURL
Every attribution traces to a published artifact. See the sources and their limits.
Nearby checks in Render & GPU
- silence renders as exact zero through the Web Audio graph
audio-render-silence-integrityThe other audio checks compare two renders of a sound; this one renders silence and asserts it stayed silent, which catches a different kind… - a constant signal renders at exactly the amplitude it was given
audio-render-dc-integrityThe companion to the silence check below, and the input that one structurally cannot see. - the same audio render reproduces itself
audio-render-reproducibilityAn OfflineAudioContext render involves no output device and no clock. - a browser that implements EME can satisfy its mandatory ClearKey baseline
eme-clearkey-baselineEncrypted Media Extensions is the DRM entry point, and it has one part that cannot be missing. - the WebGL2 element-count limits are reported beside the renderer that claims them
webgl-max-elements-readoutMAX_ELEMENTS_INDICES and MAX_ELEMENTS_VERTICES are hints the driver publishes about how many indices and vertices it draws efficiently in… - the WebGPU preferred canvas format is reported beside the claimed platform
webgpu-canvas-format-readoutnavigator.gpu.getPreferredCanvasFormat() returns the texture byte order the platform's compositor wants to be handed — and that preference… - drawing the same scene twice produces the same pixels
canvas-repeat-render-determinismCanvas noise is usually reasoned about as a trade: give up a stable hash, gain an unlinkable one. - reading the same framebuffer twice returns the same bytes
webgl-repeat-readback-determinismThe same assertion as the canvas row beside it, asked one layer down the graphics stack, and separate from it because a build can be…
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-toblob-vs-todataurl belongs to.
