Runtime.enable detection needs one Chrome DevTools Protocol command to go wrong, and on the detector we tested that command decided the verdict alone. A fresh Mosaic profile read isBot=false on deviceandbrowserinfo. The same profile with Runtime.enable sent on its page read isBot=true, with the flag isAutomatedWithCDP. Nothing else we sent over DevTools moved it. So Mosaic never sends it, and the build fails if anyone puts it back.

How Runtime.enable detection works

When a DevTools client enables the Runtime domain, Chromium starts reporting console calls to that client, and to report them it serialises each argument. The best-known check uses that: a page defines a getter on an Error's stack property, passes the error to console.debug, and waits to see whether anything read it. With no client listening, nothing does.

We have not read deviceandbrowserinfo's code, so we can't say that is exactly what it runs. What it reported was two flags, isAutomatedWithCDP and hasInconsistentTimingResolution, and the second one is a timing measurement. Either way, the check is about the connection. It does not look at the fingerprint, the user agent or the GPU, and a browser with a perfect fingerprint fails it the moment a script enables the domain.

Every profile is drawn from one real computer, not from separate listsIn Mosaic: Machines that exist. GPU, screen, CPU and fonts from one real device. Free holds 3 profiles. Pro is $49 a month for any number.

How it works: Machines that exist

What we measured

One fresh profile per row, each sending one thing over DevTools before the detector loaded:

Runtime.enable            -> isBot=true  [isAutomatedWithCDP,
                                          hasInconsistentTimingResolution]
Page.enable               -> isBot=false
Runtime.evaluate          -> isBot=false
isolated world + evaluate -> isBot=false
Input.dispatchKeyEvent    -> isBot=false

Each row is one reading. A verdict that leans on timing can vary with load, so read the table as what we saw on those runs. The pattern held, though: only one row flipped.

The third row is the useful one. Runtime.evaluate runs a script on an attached page with the domain left off. Enabling the domain only buys you the events it sends, and nothing we had written listened for them.

Five places, none of them needed

We searched our own source and found the call in five places: the warm-up robot, typing into a profile, filling a saved login, typing into many windows at once, and the shared page session. None of them needed it: each one sent the command and then never listened for a single Runtime event.

The effect was a profile that was clean at launch and became detectable the moment anyone used it. A leak test run right after launch would pass. The first warm-up, the first saved-login fill or the first line typed with human timing would switch the domain on for that page, and from then on the page could tell.

That is a nasty shape for a fault, because every check we ran before touching a page said the profile was fine. The detector only answers the question you put to it at the moment you put it.

Why our local test passed

Our first instinct was to reproduce the detection locally and test for it ourselves. It does not work. We tried reading the leak through Runtime.evaluate, through addScriptToEvaluateOnNewDocument and through an injected script tag, and none of them could see it. Each of those runs in a way the real check does not, so the local probe passed under conditions the real detector called a bot.

So the rule is written as a ban rather than as a test. npm run check:stealth fails the build if the string Runtime.enable appears anywhere in Mosaic's source. A string search is crude, and that is why it works: it cannot be satisfied by a clever probe that measures the wrong thing.

What this means if you drive a browser yourself

Playwright enables the Runtime domain on the pages it drives, and we measured that it flips this detector. If you attach a library to a Mosaic profile, the fingerprint stays what the engine made it, and the connection can still give the session away on pages that check.

What we do instead:

  1. Attach to the page target with Target.attachToTarget and leave every domain off that we do not need.
  2. Run scripts with Runtime.evaluate on that session, without enabling the domain.
  3. Send clicks and keys with Input.dispatchMouseEvent and Input.dispatchKeyEvent, which reach the page as trusted events.

The fingerprint itself is changed in C++ inside Tesseract Chrome, our own Chromium, so no script has to run on the page to hold it up. The reasons for building the engine that way are in why we built our own Chromium. The values a profile is drawn from are on real fingerprints.

A clean connection does not make a script polite. What you do on a site, and how often, is still yours to keep within its terms. If you are new to the idea of a browser per account, what an anti-detect browser is covers the parts that come before any automation.