Skip to content
On this page

Fingerprint lie detection is the practice of verifying that the signals a browser reports are internally consistent and untampered, rather than trusting them at face value. (A browser exposes hundreds of signals - the User-Agent string, the list of fonts, screen size, and so on - that together form its fingerprint.) Popularised by the open-source CreepJS project, it flips the problem: a spoofer can change any single value, but making all values agree with each other - and survive native-code integrity checks (proof a value still comes from the real browser, not a script) - is extremely hard. A detected lie is a stronger bot signal than any single fingerprint.

Quick facts

Popularised byCreepJS (abrahamjuliot)
ChecksNative-code integrity, prototype tampering, cross-property contradictions
Key trickCompare main thread vs Web Worker navigator
BeatsUA spoofing, canvas noise, property overrides
Try it/tools/browser-fingerprint-checker

The three classes of lie

1. Tampering lies. When a script replaces a built-in function - say navigator.webdriver or HTMLCanvasElement.prototype.toDataURL - the replacement no longer prints as [native code] the way a genuine browser function does. Asking Function.prototype.toString.call(fn) what the function looks like - and checking that toString itself has not been tampered with - exposes the patch. See function toString inspection.

2. Contradiction lies. Two reported values cannot both be true: a Windows User-Agent paired with a Linux font set, a navigator.platform of Win32 but a math signature (tiny rounding differences unique to each JS engine) from a different engine, userAgentData.mobile = true alongside maxTouchPoints = 0 (a touchscreen with zero touch points), or a screen availWidth larger than its width.

3. Scope lies. The most elegant: spawn a Web Worker (a background JavaScript thread) and read navigator from inside it. Many spoofing tools only patch the main-thread navigator and forget the worker scope, so the two disagree. CreepJS leans heavily on this.

Why lie detection beats spoofing

Single-value spoofing assumes the vendor reads each signal on its own. Lie detection assumes nothing and instead measures coherence - whether everything fits together. To pass, a scraper must present a fingerprint where every signal - UA, platform, fonts, canvas, WebGL renderer, math, timezone, languages, worker scope - matches one real, existing device. That is why the durable approach is to run a genuine browser on genuine hardware (or a deeply patched build like Camoufox / CloakBrowser) rather than overriding properties at runtime.

You can see exactly which lies your own browser exposes - and the trust score they add up to - in the Browser Fingerprint Checker.

Why coherence is the unit of measurement

The lesson from lie detection is that detectors measure the whole identity, not any single field. Once a detector cross-checks the User-Agent against the JS engine math, the font set against the OS, the GPU string against the renderer, and the timezone against the IP geolocation, a value changed in isolation only creates a new contradiction. Any field that differs has to be consistent with every field that did not.

This is why tools built around a real, internally consistent device profile — the approach managed scraping APIs and patched browsers such as Camoufox take — behave differently from runtime property overrides. A coherent stack (engine, fonts, canvas, WebGL, headers, network) has no seam for cross-checks to catch, whereas JavaScript overrides layered on top of a headless Chrome still surface contradictions the detector can read.

The four questions a lie detector asks

Lie detection is not one test but a small family of them, each attacking the claim from a different direction. Taken together they cover the ways a stated identity can fail to match the machine underneath.

QuestionMethodWhat a failure proves
Do two APIs agree?Ask the same question through unrelated code paths and compare - see cross-API coherence checkingOne surface was changed and another was not
Do all realms agree?Re-read the same fields inside a Worker and a fresh iframeA change was applied to the page global only
Are the built-ins genuine?Serialisation, descriptor shape, receiver brand checks, honeypot namesEngine functions were replaced by JavaScript
Does the claim match the derived values?Compare a stated browser/OS against constants no header can moveThe declared identity is not the binary that is running

The fourth is the hardest to satisfy and the least discussed. Values like eval.toString().length, navigator.productSub, and the rounding of transcendental math are derived from how the engine was compiled and which system libraries it links against, so they cannot be set per session. A stated identity has to describe the binary that is genuinely executing, or these engine and OS oracles contradict it before any fingerprint hash is even computed.

Why a lie costs more than the truth it replaces

The asymmetry at the centre of lie detection is that a declared value is one number while a coherent identity is a web of constraints. Changing the declared value is a single edit; keeping the web consistent means also moving everything that is computed from the same underlying fact, in every realm, through every code path, including paths that were never designed as identity and that nobody thinks of as part of a fingerprint.

Consider a change to the reported platform. It has to hold for navigator.platform, the Sec-CH-UA-Platform request header, the high-entropy client hint, the CSS system colors, the default font stack and its glyph advance widths, the hyphenation break points, the math library rounding, the shape of runtime error messages, and the same set of values again inside every Worker and iframe. Miss one and the identity is not merely rare - it is impossible, which is a far stronger signal.

This is why detection has shifted away from rarity scoring. A rare fingerprint has an innocent explanation: unusual hardware, an old browser, an accessibility configuration. A contradiction has none, so a site can act on it with much more confidence and far fewer false positives. The measurable consequence is that a partially modified environment often scores worse than an unmodified one, because the unmodified one is merely identifiable while the modified one is provably inconsistent.

The durable answer is to make the claim true rather than to defend it: run a real engine on a real operating system with genuine locale and network settings, so the derived values agree because they were never in conflict. That is the design principle behind engine-level browser builds, and it is what a managed web data API provides without the fleet becoming your problem.

Code example

javascript
// A compact lie detector: four independent probes, one verdict.
async function detectLies() {
  const lies = [];

  // 1. Cross-API: does the CSS engine agree with the screen object?
  if (!matchMedia(`(device-width: ${screen.width}px)`).matches) lies.push('screen-vs-css');

  // 2. Realm: does a fresh iframe report the same UA as the page?
  const f = document.createElement('iframe');
  f.style.display = 'none';
  document.body.appendChild(f);
  if (f.contentWindow.navigator.userAgent !== navigator.userAgent) lies.push('page-vs-iframe');
  f.remove();

  // 3. Integrity: is the userAgent getter still engine code?
  const get = Object.getOwnPropertyDescriptor(Navigator.prototype, 'userAgent').get;
  if (!/\{\s*\[native code\]\s*\}/.test(Function.prototype.toString.call(get))) {
    lies.push('patched-getter');
  }

  // 4. Oracle: does the claimed browser match the engine actually running?
  const claimsChrome = /Chrome\//.test(navigator.userAgent) && !/Firefox/.test(navigator.userAgent);
  if (claimsChrome && eval.toString().length !== 33) lies.push('claim-vs-engine');

  return { coherent: lies.length === 0, lies };
}
// A default headless browser is identifiable. An inconsistent one is provable.

Next in Coherence and lie detection · 2 of 8

See the general method behind every check on this page.

What Is Cross-API Coherence Checking?

Related terms

Concept map

Concept map

How Fingerprint Lie Detection connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

What is the single most common lie that gets scrapers caught?

The mismatch between the main-thread navigator and the one inside a Web Worker (a background thread). Many automation tweaks patch window.navigator but not the worker scope, and CreepJS-style checks read both and compare them.

Does a detected lie always mean a block?

Not necessarily - vendors fold it into a score rather than blocking outright. But a tampering lie (a patched built-in function) is high-confidence, so it usually pushes the session into a challenge or a block.

How do I see my own lies?

Run the Browser Fingerprint Checker. It performs native-code integrity checks, cross-property contradiction checks, and a worker-scope comparison, then reports each finding with a trust score.

Why can a partially modified browser score worse than an unmodified one?

Because the two failures are different in kind. An unmodified automated browser is identifiable - it has known characteristics a site can score probabilistically, and unusual real users produce similar scores. A partially modified one is self-contradictory, and no real device produces contradictions. Detection systems can act on a contradiction with much higher confidence, so it often triggers a harder response than the original tells would have.

Last updated: 2026-07-30 · Facts last verified: 2026-07-30