Skip to content
Benchmarks

How browser stealth is measured: coherence, entropy, and headless tells

What consistency testers, entropy tools, and headless benchmarks actually measure — and why a 'unique' fingerprint and a 'detectable' one are different problems.

Pim
Pim· Clearcote Research
9 min read

Key takeaways

  • Coherence and uniqueness are different axes: entropy tools measure how identifiable you are; consistency testers measure whether you're believable.
  • The strongest tells are consistency failures — a patched getter's non-native descriptor, or a worker reporting a different value than the main thread.
  • Injecting per-read randomness to beat hashing backfires: an unstable fingerprint is a louder bot signal than a stable, unique one.

Two different questions

"Am I detectable?" and "am I unique?" are different measurements, and conflating them is the most common benchmarking mistake. Entropy tools measure identifiability — how rare your configuration is. Consistency testers measure believability — whether your signals agree with each other and with a real device.

For an anti-detect browser, believability comes first. A perfectly common fingerprint still gets flagged if it's incoherent, while a fully coherent fingerprint that happens to be unusual just looks like a real, if uncommon, human. The goal is coherent and ideally common — not merely rare.

Consistency testers (the CreepJS model)

The open-source tester CreepJS is the de-facto stealth benchmark, and it is a coherence tool, not a uniqueness one. It reports a trust score and, more usefully, a count of "lies": places where an API was patched. It finds them by inspecting Object.getOwnPropertyDescriptor, checking functions' toString for [native code], walking prototype chains, and watching getter behaviour.

Its signature move is cross-realm re-testing: it runs the same probes in the main thread, in Web Workers, and in hidden iframes, and cross-checks them. A spoof applied only to the main-thread navigator leaves WorkerNavigator reporting the true value, and the disagreement is logged as a lie. It also compares the legacy User-Agent against UA Client Hints. "Good" here means a high trust score, near-zero lies, worker equal to main, and UA equal to UA-CH — see what CreepJS is.

Entropy: how identifiability is quantified

The other axis has an academic foundation: Peter Eckersley's 2010 Panopticlick study (now the EFF's Cover Your Tracks). Each attribute contributes surprisal−log2(P(x)) bits, where P(x) is the fraction of browsers sharing that value — and the whole fingerprint's entropy is the surprisal of the joint vector, measured against a real dataset.

The numbers are worth knowing: across 470,161 fingerprints the distribution held at least 18.1 bits of entropy, and 83.6% of browsers were unique. The high-entropy surfaces have shifted from plugins and fonts to canvas, WebGL and audio, but the method is the same. Cover Your Tracks reports "bits of identifying information" and "one in N browsers share this value" — useful for knowing which signals leak the most.

Headless-detection vectors

A headless benchmark is a checklist of boolean tells, scored as vectors triggered. The recurring ones: navigator.webdriver should be false — and a native descriptor, since a naive override is itself a lie; the WebGL renderer should be a real GPU, not SwiftShader; window.chrome should have a realistic shape; and the permissions state must be consistent (the classic contradiction is Notification.permission === 'default' while a permissions query returns denied).

A perfect score is zero vectors triggered, with every fix implemented deeply enough to survive descriptor, toString and worker inspection. This is why plain automation leaks a dozen signals, JS stealth plugins patch the surface ones but add their own recognizable residue, and only engine-level changes pass the strict version. See how headless browsers are detected.

Running a benchmark that means something

A reproducible stealth benchmark pins everything: the build, OS, screen, locale, timezone, and the seed for any per-profile randomization. It reads each test page's reported values into JSON so builds can be diffed, runs headful when validating a headful persona, and tests multiple realms and multiple origins — some tells only appear when you compare a worker to the main thread, or one site to another.

Score on every axis, not one: pass/fail per headless vector, lies at zero, cross-realm consistency, cross-layer coherence (does the JS-claimed OS agree with the TLS/HTTP-2 and the IP's geolocation?), entropy, and stability across reloads. The biggest pitfall is treating a JavaScript-only pass as a full pass — it ignores the network layer entirely — and the second is noise: adding per-read randomness to beat hashing produces an unstable fingerprint, which testers flag as a louder signal than the original. Determinism, seeded per profile, is the requirement.

The through-line

The two axes point the same way: optimize believability, then anonymity. A benchmark that only measures uniqueness rewards the opposite of what an anti-detect browser wants, and a benchmark that only runs in JavaScript misses half the surface. Measure coherence across realms and layers, keep the fingerprint stable, and prefer a common, self-consistent identity. For how that coherence is engineered, see coherence over camouflage and how we verify each build.

#benchmarks#fingerprinting#creepjs#entropy#reference

Clearcote puts this into practice

Open-source, engine-level, coherent down to the TLS handshake — a drop-in for Playwright & Puppeteer.