# Runtime.enable detection: why we never call it

> Runtime.enable detection, measured: one DevTools command turned a clean Mosaic profile into isBot=true on deviceandbrowserinfo. We removed all five calls.

Source: https://mosaicbrowser.com/blog/runtime-enable-detection

## Runtime.enable flips a detector, so we don't call it

Runtime.enable detection, measured: one DevTools command turned a clean Mosaic profile into isBot=true on deviceandbrowserinfo. We removed all five calls.

![Runtime.enable flips a detector, beside a screenshot: a Mosaic leak test: every surface a site reads, checked from inside the profile page](https://mosaicbrowser.com/covers/runtime-enable-detection.webp)

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

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)

## 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](https://mosaicbrowser.com/blog/why-we-built-our-own-chromium). The values a profile is drawn from are on [real fingerprints](https://mosaicbrowser.com/features/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](https://mosaicbrowser.com/guides/what-is-an-anti-detect-browser) covers the parts that come before any automation.

## Questions

### What is Runtime.enable detection?

A page checks whether a DevTools client has switched on the Runtime domain. When it is on, Chromium serialises console arguments for the client and reads error.stack while doing it, so a page that puts a getter on stack and logs the error learns a debugger is attached.

### Can you run Runtime.evaluate without Runtime.enable?

Yes. On an attached session Runtime.evaluate works with the domain left off. Enabling the domain only matters if you listen for Runtime events, and nothing in Mosaic does.

### Which DevTools calls left the detector clean?

In our test, one fresh profile per row, Page.enable, Runtime.evaluate, an evaluate in an isolated world and Input.dispatchKeyEvent all read isBot=false. Only Runtime.enable flipped it to isBot=true.

### How do we stop it coming back?

npm run check:stealth fails Mosaic's build if the string Runtime.enable appears anywhere in the app's source.

## Related

[Why we built our own Chromium instead of injecting JavaScript](https://mosaicbrowser.com/blog/why-we-built-our-own-chromium): Mosaic changes fingerprint values in C++ inside its own engine. Three measured differences between Chrome and plain Chromium explain why that matters.

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



[Why navigator.webdriver is true, and why false proves little](https://mosaicbrowser.com/blog/why-navigator-webdriver-true): navigator.webdriver is true because automation set it on purpose. We measured a debug-attached Chrome reading true and still scoring 100 on IPHey.

[A detector that never answered because its tab was in the background](https://mosaicbrowser.com/blog/requestanimationframe-background-tab): Background tab throttling stops requestAnimationFrame. Our test tab was in the background, two detectors never answered, and Page.bringToFront fixed both.





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