Anti-Bot

What Is Function.prototype.toString() Inspection?

By the Scrappey Research Team

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

Function.prototype.toString() inspection is a technique anti-bot scripts use to identify JavaScript functions that have been modified at runtime. Every JS function has a .toString() method that returns its source code as text. For functions built into the browser, it returns a fixed placeholder: "function name() { [native code] }". But if a plugin has replaced that built-in function with its own version, .toString() returns the replacement's actual source code instead. A two-line check spots the difference, which is why playwright-extra-plugin-stealth and similar runtime patchers are identifiable to 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
Identifiesplaywright-extra-plugin-stealth, undetected-chromedriver, puppeteer-stealth
Source-level alternativeSource-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 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 toString behaviour
  • 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.

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?

No. 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 (the encryption layer behind https) 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?

Because it addresses older or simpler bot checks — the navigator.webdriver flag, a missing chrome runtime object, an unusual plugin-list length. But on any site protected by Kasada or recent Akamai it is counterproductive: each patch it adds is individually identifiable via toString, and they stack on top of each other.

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

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

Last updated: 2026-05-31