# CreepJS prototype lies, and what they catch

> CreepJS's prototype page checks each native function's keys, source and errors. A value patched in JavaScript trips it; a value changed in C++ leaves nothing.

Source: https://mosaicbrowser.com/blog/creepjs-prototype-page

## What CreepJS's prototype page tells you about JavaScript spoofing

CreepJS's prototype page checks each native function's keys, source and errors. A value patched in JavaScript trips it; a value changed in C++ leaves nothing.

![A lie is a function that does not look native](https://mosaicbrowser.com/covers/creepjs-prototype-page.webp)

CreepJS's prototype page walks the browser's built-in objects, Navigator, Screen, canvas, WebGL, Date, Intl and dozens more, and asks each property whether it still looks native. A "lie" is any getter or method whose own keys, source text or error behaviour differ from what the real browser ships. It names the prototype and property, so a spoof written in JavaScript gets caught by name, while a value changed in the engine's C++ leaves it nothing to find.

## What the prototype page actually tests

CreepJS's lie detector lives in its source as one list of tests applied to every target it names. For `Navigator` alone the list includes `userAgent`, `platform`, `hardwareConcurrency`, `deviceMemory`, `languages`, `maxTouchPoints`, `plugins` and `webdriver`, which are exactly the properties a fingerprint spoof wants to change.

For each one it reads the getter or method and asks questions a real native function answers the same way every time:

- Does `Function.prototype.toString` print it as `[native code]`, and does `toString` itself print as native?
- Are its own property names exactly `length` and `name`? A function written in JavaScript usually has `prototype` too, and sometimes `arguments` or `caller`.
- Does `'prototype' in fn` come back false, as it does for a native getter?
- Does calling it the wrong way throw the right `TypeError`: `new fn()`, `fn.call()` on the prototype, `class extends fn`, reading the getter straight off the prototype?
- Does it behave like a Proxy? Setting its prototype to itself should throw a recursion error, and `Object.create(fn).toString()` should fail with the stack trace a native function produces.

Every failure is recorded with its name, `failed toString`, `failed own property names`, `failed at too much recursion error` and so on, against the prototype and property it came from. A clean browser has an empty list.

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 the iframe matters

The `toString` those checks call is not the page's. CreepJS creates a hidden iframe, takes that frame's own `Function.prototype.toString`, and calls it on the page's functions. A script that blocks the iframe gets that written down too, as an error of its own.

We learned what that costs the hard way. Before our engine, we spoofed fingerprints with a script on stock Chrome, and for a while it hid its patched functions with a rewritten `toString` that answered from a lookup table of our own functions. In the page, airtight. One realm over, an iframe had its own `toString` and its own empty table, so the fallback ran and printed each function's real JavaScript body. Measured on one profile with a four-line probe, thirty-three patched functions across Navigator, Screen, canvas, WebGL, audio and client rects gave up their source.

The repair was to make each patch a `Proxy` whose target is the real native function. V8 prints a proxy over a native as native code in every realm, and the target's `name`, `length` and own property names come along. That fixed the iframe test and opened another: CreepJS's proxy checks exist because a Proxy has its own tells. Ours had one more. With `toString` itself installed as a proxy over the real `toString`, it recursed until the stack overflowed on a bot-check page, which threw `Maximum call stack size exceeded` four times and never showed its verdict. The fix was to point that one proxy at a different target, a plain method rather than the real `toString`, shaped so that `'prototype' in Function.prototype.toString` still answers false, as stock Chrome does.

Each of those was a property CreepJS's list checks. The list is long because a spoof has that many ways to differ.

## Why an engine change leaves nothing to flag

Every test on the prototype page asks one question: is this the browser's own function? When the value is changed inside the browser, the answer is yes. On Tesseract Chrome, our engine, `navigator.hardwareConcurrency` is still Chromium's own getter. It returns a different number because the C++ behind it was patched to, and nothing in the page was replaced. Its `toString` is native because it is native. It has no extra keys, throws the right errors, and gives the same answer in the iframe, because the iframe asks the same C++.

So the prototype page is close to a direct measure of where a spoof lives. Values set in JavaScript show up there, nearly always, unless every one of those tests was anticipated for every patched property in every realm. Values set in the engine don't show up, because there is nothing to show. That is the main reason Mosaic changes fingerprints in the engine and injects no fingerprint script on its own engine: two spoofs of one value, one in C++ and one in JavaScript, would put a lie back on this page.

## How we read it in our own catalogue

Our detection catalogue runs this page as an automatic row: pass means no property flagged, and a flagged entry names the exact prototype and property, which is enough to fix it without guessing. That row once came back `unread` for a reason that had nothing to do with CreepJS. Our extractor was a regular expression inside a template literal, which ate its backslashes, so it matched text the page never prints. Once the extractor parsed, the row read the page's real answer. When a detector looks silent, check the probe before the browser.

To see your own browser's values, the [fingerprint checker](https://mosaicbrowser.com/tools/fingerprint-checker) shows what a page can read. How each Mosaic profile's values are drawn from one real device, instead of being patched one at a time, is on [real fingerprints](https://mosaicbrowser.com/features/real-fingerprints), and [what an anti-detect browser is](https://mosaicbrowser.com/guides/what-is-an-anti-detect-browser) covers the difference between spoofing in the page and changing the browser. Our engine itself was announced in [introducing Mosaic](https://mosaicbrowser.com/blog/introducing-mosaic).

## Questions

### What is a prototype lie in CreepJS?

A native browser function or getter that does not behave like the browser's own: wrong source text from toString, extra own properties, a missing TypeError, or a trace of a Proxy. CreepJS records the prototype and property name of each one.

### Why does CreepJS use an iframe for its checks?

A fresh iframe has its own copy of Function.prototype.toString. A spoofing script that taught the page's toString to hide its functions usually did nothing to the iframe's copy, so CreepJS calls the iframe's toString on the page's functions and gets the real source.

### Can a JavaScript spoof pass the prototype page?

Some can, with a lot of care: a Proxy over the native function, the right name and length, and the same answers from every realm. Each surface patched is another chance to get one of those wrong, and the page checks dozens of them.

### Does Mosaic spoof anything in JavaScript?

Not on Tesseract Chrome, Mosaic's own engine. Fingerprint values are changed in the engine's C++, so the functions a page inspects are the browser's own and the prototype page has nothing to report.

## Related

[Machines that exist: fingerprints drawn from real hardware](https://mosaicbrowser.com/features/real-fingerprints): Mosaic draws each profile's GPU, screen, CPU, memory and fonts from one real device, so the parts of a fingerprint agree with each other and with the host.

[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.
