The two-line detection
The entire technique is:
const looksLikeNative = (fn) =>
Function.prototype.toString.call(fn).includes('[native code]');A real browser's navigator.webdriver getter, HTMLCanvasElement.prototype.toDataURL, WebGLRenderingContext.prototype.getParameter — every native method passes this check. A stealth plugin that replaces any of those with a JS function fails it immediately because the replacement function's source code appears verbatim.
Anti-bot scripts run this check across dozens of methods. Even one failure is enough to score block-grade. 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 idea is to patch Function.prototype.toString itself to return "[native code]" for stealth-replaced methods. This fails for two reasons. First, the patched toString is itself a function whose toString() can be inspected — turtles all the way down. Second, anti-bot scripts call Function.prototype.toString.toString() and check it matches the canonical native signature, which it doesn't once patched.
You also cannot use a Proxy to wrap functions because Proxy reveals itself: fn.toString on a proxied function returns a different value than the original (and creates a new own-property), which is itself detectable.
What actually works
The only durable fix is patching below the JavaScript engine, at the C++ source of Chromium / Firefox. Tools that do this:
- Camoufox — Firefox fork with C++-level patches to the engine's function-source-mapping
- CloakBrowser — Chromium fork with 49 documented C++ patches including
toStringbehaviour - PatchRight — patches the Playwright Python source so the injected scripts don't exist as JS at all
These tools modify the engine so that when Function.prototype.toString is called on a method, the engine returns the original [native code] string from the function's metadata regardless of whether the underlying behaviour was changed. Because the patch is in C++, there is no JS function to inspect.
