# CreepJS lies detected: what a lie is and why Chrome has none

> CreepJS lies detected means an API behaved unlike the native one: wrong toString, wrong descriptor, or a worker that disagrees. Stock Chrome has none.

Source: https://mosaicbrowser.com/blog/what-a-creepjs-lie-is

## What a CreepJS lie is, and why stock Chrome has none

CreepJS lies detected means an API behaved unlike the native one: wrong toString, wrong descriptor, or a worker that disagrees. Stock Chrome has none.

![0 lies: A CreepJS lie, and why stock Chrome has none](https://mosaicbrowser.com/covers/what-a-creepjs-lie-is.webp)

A CreepJS lie is a browser API that does not behave like the native one it pretends to be. CreepJS calls a getter such as `navigator.hardwareConcurrency`, then inspects the function behind it: its source text, its property descriptor, the error it throws when misused, and the value it gives in a worker. When "lies detected" is above zero, at least one of those disagreed with an unmodified browser. Stock Chrome has none, because there is nothing in it that was replaced.

## What CreepJS checks when it hunts for lies

A spoof done in page JavaScript has to replace a function the browser wrote in C++. The replacement can return any value it likes, but it is a different kind of object, and CreepJS asks it questions only a native function answers correctly.

- Call `toString()` on it. A native getter prints `function get hardwareConcurrency() { [native code] }`. A plain replacement prints its own source.
- Read its own properties. Native methods have no `prototype` and no `arguments` or `caller`; ordinary JavaScript functions often do.
- Misuse it. Calling a native getter on the wrong object throws a `TypeError`, and calling a method with `new` throws too. A replacement that forgets to throw, or throws the wrong error, is caught.
- Look for a `Proxy`. Wrapping the native in a `Proxy` fixes `toString`, so CreepJS pokes at the edges a proxy cannot hide: cyclic prototype errors, odd stack traces, and how the object looks when reached through a second realm.
- Ask again somewhere else. CreepJS re-reads values from an iframe and from a dedicated worker, a shared worker and a service worker, and compares them with the page.

The last one catches spoofs that pass every other test. A script injected into the page often never reaches a service worker, so the page says 8 threads and the worker says the machine's real 12. Each of those disagreements is listed as a lie against the API it was found on.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Machines that exist](https://mosaicbrowser.com/features/real-fingerprints)

## Why lies detected on CreepJS is worse than a low score

Our detector catalogue in Mosaic has a main CreepJS row, and its pass rule is written like this: a trust score above 70%, "and crucially lies at 0. A high score with lies detected is worse than a mediocre score without: it means the spoofing itself is the signal."

The reason is what a lie is evidence of. A middling trust score says the browser is unusual, which plenty of real browsers are. A lie says somebody rewrote this browser's functions, and real browsers almost never look like that. A site that sees one does not need to know what the true value was. It already knows the browser is wearing something.

## Why stock Chrome has none

There is no lie to catch in an unmodified Chrome because every getter is the real binding. Its `toString` is native because it is native. Its descriptor, its errors and its missing `prototype` are whatever Chrome ships, because Chrome is the thing shipping them. And every realm reads one value from the same C++ code, so the worker cannot disagree with the page.

That is the whole reason Mosaic changes fingerprint values inside its engine, Tesseract Chrome, and not in page JavaScript. When the engine reports a different thread count, the getter a page inspects is still Chrome's own, and the page, its iframes and its workers get the same number. The values a page can read, and which of them a detector uses first, are on our [fingerprint checker](https://mosaicbrowser.com/tools/fingerprint-checker).

## Where we found one in our own engine

Changing values in C++ removes the `toString` class of lie. It does not remove the cross-realm class, and we found that out the slow way.

CreepJS's worker page hashed each realm's canvas output. A stock Chrome of the same major agreed with itself everywhere. Ours did not:

```
              Window      Dedicated   Shared      Service
stock         296ab2ea    296ab2ea    296ab2ea    296ab2ea
Tesseract     296ab2ea    41f7870c    41f7870c    41f7870c

```

The window matching stock looked like a clean window. It was the opposite. On the main thread, Chrome encodes a PNG through a progressive idle encoder that reads the canvas rows directly and never reached the code where our canvas noise lived, so `toBlob` in the page returned a file byte-identical to stock Chrome's. Workers took the other branch and were noised. Patch `0136` now stamps the pixels once, where every encode path reads them, and the worker page then reported 3 worker realms agreeing with the window across 45 fields. How canvas output is hashed and compared is on the [canvas fingerprint](https://mosaicbrowser.com/tools/canvas-fingerprint) page.

Two smaller lessons came with it. CreepJS paints its worker panels red when the whole-block hashes differ, and the window's block carries fields no worker has, so the panels are red on a browser with nothing wrong. Our extractor compares realms field by field instead. And a value that matches stock in one realm proves only that it matches stock; it may mean nothing was applied there at all.

## How to read your own CreepJS result

Read the lies count before the trust score. If it is above zero, find which API CreepJS named and ask what changed it: an extension, a stealth plugin, or a browser that spoofs in JavaScript. Then run the page twice, because one reading of a live detector is one observation.

If you are comparing products, [what an anti-detect browser is](https://mosaicbrowser.com/guides/what-is-an-anti-detect-browser) covers the difference between changing values in the engine and injecting them into the page, and [our launch post](https://mosaicbrowser.com/blog/introducing-mosaic) explains why we chose the engine.

## Questions

### What does "lies detected" mean on CreepJS?

CreepJS found browser APIs that do not behave like the native ones the browser claims to have. Usually that means a script or extension replaced a getter, and the replacement gave itself away through its source text, its property descriptor, the error it throws, or a worker that reports a different value.

### Is a high trust score with lies a good result?

No. Our catalogue's pass rule for CreepJS is a trust score above 70% and lies at 0, and the second half matters more. A lie means the spoofing itself is visible, and almost no real browser carries a patched native function.

### Why does an unmodified Chrome show no lies?

Every function it exposes is the real native binding, so its source text, descriptor and errors are the ones CreepJS expects, and every realm reads the same value from the same C++ code. There is nothing for the comparison to catch.

### Does Mosaic change fingerprint values with JavaScript?

Not on its own engine. Tesseract Chrome moves fingerprint values in C++, so the accessor a page inspects is still Chrome's native one, and the page, iframes and workers all read the same answer.

## Related

[What is an anti-detect browser?](https://mosaicbrowser.com/guides/what-is-an-anti-detect-browser): An anti-detect browser gives each profile its own fingerprint, proxy and cookies, so sites see separate visitors. What it changes, how, and its limits.

[Introducing Mosaic, an anti-detect browser that lives on your machine](https://mosaicbrowser.com/blog/introducing-mosaic): Mosaic runs its own Chromium, draws each profile from a real machine and matches it to the proxy exit. Profiles stay on disk. One plan, no per-profile fees.













## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. Free holds 3 profiles. Pro is $49 a month for any number.

Mosaic is a desktop app

Email yourself the link and open it on your computer.
