CreepJS doesn't read one spoofed value — it cross-checks trusted and untrusted sources for the inconsistencies most spoofing tools can't fully hide.
Most fingerprinting tools answer one question: how unique is this browser? CreepJS, an open-source fingerprinting research project, asks a different one: is this browser telling the truth? Rather than just collecting a Canvas hash or a WebGL renderer string and moving on, it cross-checks each signal against every other signal the same browser exposes — and against how a genuine implementation of that signal is supposed to behave. When a value looks plausible on its own but contradicts something else the browser reveals, CreepJS calls that a lie, and its trust score drops accordingly. This article explains that lie-detection model in general terms: what "trusted" and "untrusted" sources mean, how tampering with a prototype or getter gives itself away, and why the same class of check trips up anti-detect browsers and naive user-agent spoofing alike.
Key Takeaways
- CreepJS's core idea is consistency, not identity: it doesn't need a database of "correct" values, only proof that a browser's own signals contradict each other or contradict how a real implementation behaves.
- It compares trusted and untrusted sources for the same fact — reading a property directly versus deriving it indirectly, or comparing a main-thread result against the same computation run in a Worker — and flags disagreement between them.
- Prototype and getter tampering, the standard way spoofing tools override values like
navigator.userAgentorHTMLCanvasElement.prototype.toDataURL, leaves structural evidence: altered property descriptors,.toString()output that no longer matches native code, and reference identity that shifts across calls. - Proxy traps used to intercept property access are themselves detectable, because a genuine browser API doesn't need a
Proxywrapper to answer a query. - The same lie-detection logic is why naive user-agent or Canvas spoofing — and even well-built anti-detect browsers — tend to leave a pattern of contradictions rather than a single obvious tell.
What CreepJS Actually Tests
CreepJS's stated purpose, per its GitHub repository, is to "shed light on weaknesses and privacy leaks among modern anti-fingerprinting extensions and browsers." It runs a broad battery of checks — canvas and WebGL rendering, audio and font availability, CSS computed styles, screen and timezone data, JavaScript runtime characteristics, and more — and instead of only hashing the results into a single identifier, it evaluates each one for internal plausibility. The output is a trust score alongside an itemized breakdown: which signals look native, and which look assembled, patched, or otherwise inconsistent with a genuine browser instance.
That framing matters for anyone auditing their own setup. A fingerprint-uniqueness tool tells you how identifiable you are. A lie-detection tool tells you whether the identity you're presenting actually holds together — which is closer to what bot-mitigation, fraud-scoring, and anti-detect-browser detection systems check in production, as covered in how sites detect anti-detect browsers and fingerprint spoofing.
The Lie-Detection Model: Consistency, Not Identity
A naive fingerprinting check reads a value and records it. A lie-detection check reads the same fact through more than one path and compares the answers. If a browser genuinely is what it claims to be, every path to the same fact should agree, because they all ultimately derive from the same engine, the same rendering pipeline, and the same operating system. If something has been patched to present a false value, it's very hard to patch every path consistently — and the paths that were missed are where the lie surfaces.
This is the same principle behind fingerprint consistency checking generally: uniqueness measures how rare a fingerprint is, while coherence measures whether it could plausibly belong to one real device. CreepJS applies that coherence idea at a finer grain — not just "does the GPU match the claimed OS" but "does this specific API's low-level behavior match what a real implementation of that API produces," repeated across dozens of individual checks.
Trusted vs. Untrusted Sources: Where the Lies Get Caught
A useful way to think about this class of tool is in terms of trusted and untrusted sources for the same underlying fact. A trusted source is one that's expensive or awkward for a spoofing script to intercept — a value read from a different execution context, computed as a side effect of unrelated code, or derived from behavior rather than from a property a script can simply reassign. An untrusted source is the obvious, commonly-spoofed property itself — navigator.userAgent, a Canvas toDataURL() call, a WebGL parameter — the ones every anti-detect tool and privacy extension already knows to intercept.
The check isn't "is the untrusted value real" in isolation; it's "does the untrusted value agree with the trusted one." A few examples of the shape this takes generally:
- Main-thread vs. Worker context. The same computation — a Canvas draw, a timezone read, a math operation — can be run both on the main thread and inside a Web Worker. A real browser produces the same result in both places, because both execution contexts share the same underlying engine. Spoofing tools frequently patch only the main-thread global object and miss the Worker's separate global scope, so the two contexts disagree. The cross-context Canvas consistency check — comparing
OffscreenCanvasoutput in a Worker against a document-attached canvas — is a specific instance of this general pattern. - Direct read vs. derived value. Some facts can be read directly from a property and also inferred indirectly — from a side effect, an error message, or a piece of behavior that only a genuine implementation would produce correctly. If the direct read and the derived value disagree, only one of them can be telling the truth, and it's rarely the one that's trivial to patch.
- Declared identity vs. observable behavior. A user-agent string claims a browser and engine; JavaScript engine timing, error-message wording, and feature presence reveal which engine is actually running, regardless of what the string says — the exact mechanism explained in how to detect user-agent spoofing.
None of these checks require CreepJS to know what your "correct" fingerprint should be. It only needs two independent windows onto the same fact, and the confidence that a real browser can't help but show the same answer through both.
Property Descriptors, Getters, and Proxy Traps
Beyond cross-source comparison, CreepJS's methodology includes direct prototype tampering detection — looking for structural evidence that a browser API has been overridden rather than merely checking whether its return value looks plausible.
Spoofing a value like navigator.userAgent or a Canvas method typically means redefining a getter or reassigning a prototype method. That intervention changes things beyond the value itself:
- Property descriptors.
Object.getOwnPropertyDescriptor()on a native method returns a specific, predictable shape, and the method's.toString()reads"function name() { [native code] }". A naively patched version often returns a visible function body instead, or an override that forgot to synthesize the native-code marker. - Proxy traps. Intercepting property access with a
Proxyis a common way to patch getters dynamically, but a live Proxy behaves differently under referential-equality checks than a native property — accessing it twice can return distinguishable wrapper instances, or itsget/hastraps respond to probes a native property never needs to answer. - Reference identity. A genuine prototype method is the exact same function object every time it's accessed. A patched version implemented as a fresh closure or generated wrapper can fail an
===comparison across repeated accesses, something a native implementation never does.
None of this proves what the real value should have been — only that the property answering the query isn't the one the browser shipped with. That's frequently a stronger signal than trying to guess whether a specific Canvas hash or GPU string "looks right," because it doesn't depend on a database of known-good values at all.
Why Naive Spoofing and Anti-Detect Browsers Still Fail
The recurring theme across all of these checks is that spoofing one signal is easy, and spoofing an entire coherent set of trusted and untrusted sources consistently is not. A script that overwrites navigator.userAgent rarely also patches the Worker-context equivalent, adjusts every downstream property descriptor, and replicates native-code .toString() output for the getter it just installed. Anti-detect browsers — purpose-built Chromium forks designed to coordinate spoofed values across a whole profile — get closer, but even they tend to leave the same class of seam: a value patched on the main thread but not inside a Worker, a Proxy trap that responds slightly differently than a native property, or a claimed identity that a trusted-source check quietly contradicts.
This is evergreen: the specific checks a fingerprinting research tool runs will keep expanding as engines and spoofing techniques evolve, but the underlying model — read the same fact through a trusted and an untrusted path, and treat disagreement as evidence — doesn't go out of date. It's the same reasoning that makes coherence-based detection durable against one-off tricks: a detector doesn't need to enumerate every possible lie, only to keep multiplying the number of independent paths a spoofing tool has to patch identically.
Checking Your Own Browser
BrowserInsight's own tools apply this same consistency-first thinking, without asking you to interpret a research-grade fingerprint dashboard. The fingerprint check surfaces your Canvas, WebGL, and font signals alongside the coherence checks that flag when they don't line up; the bot detection tool runs the automation- and consistency-focused checks a mitigation system would run in production. If you want a step-by-step version of the same self-audit, Browser Fingerprint Consistency: A Self-Check Checklist walks through seven concrete UA/GPU/timezone/font checks you can run in a couple of minutes. None of this is a guide to passing CreepJS or any other lie-detection tool — the point of understanding the model is to see why coherence, not any single value, is what actually gets audited.
Frequently Asked Questions
Does CreepJS collect or store my fingerprint?
CreepJS is a client-side research tool you run in your own browser to see what it reveals about you; the point of running it yourself is to audit your own signals, not to be tracked by it. Always check a tool's own privacy policy before relying on any hosted fingerprinting service.
Can a well-built anti-detect browser fool a lie-detection tool like CreepJS?
Against a subset of checks, sometimes. Coordinating every trusted and untrusted source consistently — main thread and Worker context, every property descriptor, every derived side effect — across a constantly evolving set of checks is difficult to sustain, which is why anti-detect setups tend to leave at least one seam even when carefully built, as discussed in how sites detect anti-detect browsers and fingerprint spoofing.
Is a low trust score proof that a real user is a bot or fraudster?
No. A lie-detection score measures fingerprint coherence, not intent. Privacy extensions, older browser builds, and unusual but legitimate configurations can all trip individual checks. Production systems combine this signal with others rather than treating it as a standalone verdict.
How is lie-detection different from ordinary fingerprint uniqueness testing?
Uniqueness testing (the EFF's Cover Your Tracks is a well-known example) measures how identifiable your combined fingerprint is among other visitors. Lie-detection instead asks whether the individual signals composing that fingerprint are internally consistent with each other and with genuine browser behavior — a different question that doesn't require comparing you against anyone else's fingerprint at all.


