A site rarely catches an anti-detect browser by its values. An Apple M4 Max, 16 threads and a Berlin timezone are all ordinary. It catches the browser when two of its values could not both be true. Every way sites detect anti-detect browsers below is a version of that comparison, and we found most of them by failing them on our own engine before we fixed them.

Where the page and its workers disagree

A page is not one place a script runs. Dedicated workers, shared workers and service workers each have their own copy of navigator, their own canvas and their own WebGL, and a page can ask any of them for the same value. The open-source test CreepJS does exactly that and compares the answers.

That is where tools that change values with injected JavaScript fall over. A script added to the page can redefine navigator.hardwareConcurrency in the window. It cannot reach a service worker, and a worker started from a blob: URL often starts before the script does. So the window says 8 threads and the worker says 16, and the disagreement is worth more to a detector than the real number would have been.

We measured our own engine against this before it was finished. A stock Chrome encoded the same canvas to identical bytes in the window and in a worker. Ours matched stock in the window and differed in the worker, because our canvas noise reached the worker's encoder and not the page's. We fixed it in the engine, and CreepJS's worker check then read "3 worker realms agree with the window across 45 fields". That is why Mosaic changes nothing with page scripts: a value moved in C++ inside the browser is the same in every realm, because there is only one copy of it.

A GPU name the driver does not back up

Changing the WebGL renderer string is easy. Changing what the GPU actually does is not. A page can read the extension list, the shader precision formats, how long a draw call takes and the exact pixels it produces, and none of that moves when the name does.

We measured how strict this is on Pixelscan, one profile patched in place, on an Apple M4 Max Mac. Claiming the host's own chip passed with "fingerprint consistent, no masking". Claiming an Apple M2 Pro, M1, M4 Pro or plain M4 failed with "masking detected", every round. Not the family, not the generation: the exact chip. So Mosaic draws macOS profiles with the host's own GPU and varies everything that can honestly vary.

The fingerprint is changed inside the engine, not painted on topIn Mosaic: Its own Chromium. Tesseract Chrome: values changed in C++, not JavaScript. Free holds 3 profiles. Pro is $49 a month for any number.

How it works: Its own Chromium

Fonts from a different operating system

A browser claiming Windows has Windows fonts, measured by how wide the text renders. Fonts and the way the operating system rasterises them come from the real computer, and no browser switch reaches them. On our macOS build machine, a profile claiming Linux read "Masking detected" on Pixelscan with 1 font detected against 159; one claiming Windows never finished the scan.

That is why Mosaic, which will make a Windows or Linux profile on a Mac, shows a caution in the editor when you do: on the same Mac, IPHey scored a macOS claim 100/100 and a Windows claim 80/100. Any browser that lets one computer claim another desktop operating system has to answer this contradiction somehow, and it is worth asking how. The detail of what Pixelscan compares is in Pixelscan's inconsistent fingerprint warning.

A Chromium build claiming to be Chrome

Most anti-detect browsers are Chromium, and most claim to be Google Chrome. The two differ in ways a page can count:

  • speechSynthesis.getVoices() returns 199 voices in branded Chrome on a Mac and 180 in an unbranded build, because 19 Google … voices ship only with Google's build.
  • An unbranded Chromium runs field-trial settings that Google's Chrome does not. Measured against Chrome of the same version, that was 20 API differences, such as navigator.runAdAuction missing and window.ModelContext present.
  • Without Google's signatures, the first Widevine DRM request carries a per-install device certificate: 1,704 bytes where branded Chrome sends 2. The certificate is stable per install, so it ties every profile on the machine together.

A useful control: Google's own unbranded Chrome for Testing, with no patches at all, earned the same IPHey warning as our build did. Some of what detectors flag is "this is Chromium", not "this is spoofed". Our engine now restores the voices, turns the field-trial config off and leaves Widevine off unless it is switched on.

The network disagreeing with the page

The TLS handshake has its own fingerprint, JA3 or JA4, which Cloudflare exposes to its customers' rules (Cloudflare docs). A browser whose User-Agent says Chrome and whose handshake looks like an HTTP library is caught before the page loads. Measured side by side on one machine, our profile and Google's Chrome for Testing of the same version produced the same JA4 string, because the engine is Chromium's own network stack.

Then the address. A Berlin exit with a Los Angeles timezone and an English-only language list is the first comparison a consistency scanner makes, which is why Mosaic sets timezone, language and geolocation from the proxy's measured exit.

Automation that announces itself

navigator.webdriver is true in a browser under automation (MDN), and headless Chrome names itself in its User-Agent. Subtler: Puppeteer and Playwright switch on the DevTools Protocol's Runtime domain, and pages can notice. On a fresh profile, enabling Runtime alone flipped deviceandbrowserinfo's verdict from isBot=false to isBot=true. Mosaic's own page control reads and clicks without ever enabling it.

Values that are identical across every profile

The last method is statistical. If every profile from one tool reports the same unusual value, that value marks the tool. We had one: Notification.permission read denied on all 344 profiles on our build machine, because a setting was forced for every launch, while an unconfigured Chrome says default. Nothing leaked in. One value was pinned across every profile, which a site reads as well as any leak.

How sites detect anti-detect browsers, and what to ask before you pick one

Ask how the tool changes values, not how many it changes. A page script cannot reach workers. A foreign operating system cannot bring its fonts. A renamed GPU keeps its driver. Then test it yourself on several detectors, on more than one day, because one verdict from a live site is not evidence. Our own engine is described on the engine page; what a fingerprint is made of is in the browser fingerprint glossary entry.

Sources

  1. CreepJS README on GitHub, read November 2, 2026
  2. Pixelscan: fingerprint check, read November 2, 2026
  3. Cloudflare docs: JA3/JA4 fingerprint, read November 2, 2026
  4. MDN: Navigator.webdriver, read November 2, 2026