The two-line detection
The whole technique fits in two lines:
const looksLikeNative = (fn) =>
Function.prototype.toString.call(fn).includes('[native code]');The text [native code] is the browser engine's way of saying "this function is built in, I'm hiding its real source." A genuine browser's navigator.webdriver getter, HTMLCanvasElement.prototype.toDataURL, and WebGLRenderingContext.prototype.getParameter all pass this check. A stealth plugin that replaces any of those with its own JavaScript function fails instantly, because the replacement's source code shows up word-for-word instead of the placeholder.
Anti-bot scripts run this check across dozens of methods, and even one failure is enough to score block-grade (high enough to get you blocked). Once you know to look, playwright-extra-plugin-stealth patches more than 20 detectable surfaces — each one a clear bot signal.
Why naive workarounds fail
The obvious fix is to also patch Function.prototype.toString itself so it returns "[native code]" for any function a stealth plugin replaced. This fails for two reasons. First, the patched toString is itself a function whose .toString() can be inspected — it's turtles all the way down. Second, anti-bot scripts call Function.prototype.toString.toString() and check that it matches the real native signature, which it no longer does once patched.
You also cannot wrap functions in a Proxy (a JavaScript object that intercepts calls), because a Proxy gives itself away: fn.toString on a proxied function returns a different value than the original (and adds a new own-property), and that difference is itself detectable.
What actually works
The only durable fix is to patch below the JavaScript engine, in the C++ source code of Chromium or Firefox itself. Tools that do this:
- Camoufox — a Firefox fork with C++-level patches to the engine's function-source mapping
- CloakBrowser — a Chromium fork with 49 documented C++ patches, including
toStringbehaviour - PatchRight — patches the Playwright Python source so the injected scripts never exist as JS in the first place
These tools change the engine so that calling Function.prototype.toString on a method returns the original [native code] string from the function's metadata, no matter how the underlying behaviour was changed. Because the patch lives in C++, there is no JavaScript function left to inspect.
