Skip to content
All fingerprint checksRender & GPU

canvas readback is not noise-injected (1×1 probe)

Check id canvas-tamper

What an ordinary browser yields

A pixel filled pure black reads back pure black.

What a detector infers

The probe fills a single pixel with pure black on a 1x1 canvas and reads it straight back with getImageData. An untouched pipeline returns exactly (0,0,0); a noise-injecting farble perturbs the value and it comes back non-black. This is a zero-baseline oracle — the expected answer is known a priori, so any site can run it in a few lines and needs no reference corpus. It distinguishes two very different properties: "this canvas output is unique" (ordinary, every machine differs) versus "this canvas output has been modified" (a deviation from the platform's defined behaviour). If navigator.brave is present and the pixel is perturbed, the check returns N/A and is not scored: Brave ships farbling as a stock privacy feature, so there the noise identifies the browser rather than indicating a lie.

How to resolve it

Where canvas output is scored, turn canvas noise off (fingerprint_noise=false / --disable-fingerprint-noise) or route readback through a real GPU with the canvas bridge, so the output is genuinely produced rather than perturbed after the fact. Randomised canvas output resolves a uniqueness problem while creating a coherence one.

What is canvas fingerprinting?

Read in the wild by

CreepJS

CreepJS, an open research and education demo rather than a production detector, fills a canvas with random rgba values it records and reads the pixels straight back with getImageData to report which colour channels came back altered, and separately flags a cleared canvas whose pixels do not read back as zero.

src/canvas/index.ts `getPixelMods()` (L11-L126): fills an 8x8 canvas from `const red = ~~(Math.random() * 256)` (L49-51) via `context1.fillStyle = `rgba(${colors})`; context1.fillRect(x, y, 1, 1)` (L53-54), recording written colors with `pattern1.push(colors)` (L63); reads them straight back with `const { data: [red, green, blue, alpha] } = context1.getImageData(x, y, 1, 1) || {}` (L69-71) into `pattern2` (L93), then diffs written-vs-readback into `rgbaChannels` / `patternDiffs` (L97-98, L112-113). Separately, on a canvas cleared at L439: `if ((mods && mods.pixels) || !!Math.max(...context.getImageData(0, 0, 8, 8).data))` (L440) triggers `documentLie('CanvasRenderingContext2D.getImageData', 'pixel data modified')` (L442). Self-description, README.md L91: "The goal of this project is to conduct research and provide education, not to create a fingerprinting library."

Akamai

Renders text and gradients to a hidden canvas and hashes the pixels.

signal_categories.md - 3. Canvas Fingerprinting

Edioff/akamai-analysis — signal_categories.md

DataDome

Ships a canvas hash packed together with the screen dimensions and colour depth.

KurKFS = "8cd0430a…c60796_1920_1055_8"

glizzykingdreko/datadome-encryption — tests/original.json

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 canvas-tamper belongs to.