Anti-Bot

What Is Function.prototype.toString() Inspection?

What Is Function.prototype.toString() Inspection? — conceptual illustration
On this page

Function.prototype.toString() inspection is the technique anti-bot scripts use to detect runtime JavaScript patches. Every JS function exposes a .toString() method. For browser-native methods it returns "function name() { [native code] }". For a function replaced by a stealth plugin it returns the replacement's actual source code. A two-line check distinguishes the two, which is why playwright-extra-plugin-stealth and similar runtime patchers lose against vendors that use this technique — most notably Kasada.

Quick facts

The core checknavigator.webdriver.toString().includes("[native code]")
Vendor flagshipKasada — built the entire detection model around toString inspection
Defeatsplaywright-extra-plugin-stealth, undetected-chromedriver, puppeteer-stealth
Bypass approachSource-level patching (PatchRight, CloakBrowser, Camoufox) below the JS layer
Why it workstoString() runs in the engine, not as JS — cannot be cheaply spoofed

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 toString behaviour
  • 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.

Code example

javascript
// What Kasada's ips.js runs — paraphrased, the real version is obfuscated
const SUSPECT_METHODS = [
  navigator.webdriver,                                   // most-checked
  HTMLCanvasElement.prototype.toDataURL,
  HTMLCanvasElement.prototype.getContext,
  WebGLRenderingContext.prototype.getParameter,
  CanvasRenderingContext2D.prototype.getImageData,
  Permissions.prototype.query,
  Notification.requestPermission,
  Function.prototype.toString                            // the meta-check
];

const patched = SUSPECT_METHODS.filter(fn => {
  if (typeof fn !== 'function' && typeof fn !== 'undefined') return false;
  if (fn === undefined) return false;
  return !Function.prototype.toString.call(fn).includes('[native code]');
});

if (patched.length > 0) {
  // Send block-grade signal back to the anti-bot endpoint.
  // playwright-extra-plugin-stealth fails roughly 8 of these on default settings.
}

Related terms

Concept map

How Function.toString() Inspection connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

Does every anti-bot vendor use toString inspection?

Kasada makes it the centerpiece. Akamai's sensor.js runs the check on a smaller set of methods. DataDome and PerimeterX inspect a handful of methods as part of their broader fingerprint. Cloudflare Bot Management leans more on TLS and behaviour than on toString. Imperva and AWS WAF Bot Control don't use it heavily.

Why is playwright-extra-plugin-stealth still recommended in tutorials?

It defeats older or simpler bot checks (the navigator.webdriver flag, missing chrome runtime, plugin list length). On any site protected by Kasada or recent Akamai, it makes things worse — the patches it adds are each individually detectable via toString and stack on each other.

Is there a way to detect whether the current site uses toString inspection?

Open DevTools, breakpoint on Function.prototype.toString, then reload. If the breakpoint fires hundreds of times before the page is interactive, the site is running an aggressive inspection pass.

Last updated: 2026-05-27