All posts

Part 4 · Browser

Stealth mode: patch nothing that isn't already a tell

Most stealth plugins hide automation by rewriting browser APIs. Modern fingerprinters look for exactly that rewriting. Slapify's stealth mode takes the opposite approach, and on a current Chrome it ends up changing almost no JavaScript at all.

An agent that works on the real web meets bot detection sooner or later. Our Twitter agent ran headless for days, and a headless browser announces itself in several ways: its user agent literally says HeadlessChrome, navigator.webdriver can report true, and a few other properties look nothing like a person's browser. Turning on stealth is one line:

browser:
  headless: true
  stealth: true
  profile: .slapify/browser-profile   # a real, persistent profile helps too

or slapify task "…" --stealth for a single run.

Before and after

This is bot.sannysoft.com, a well-known page of automation checks, loaded by the same headless browser twice: once plain, once with stealth. Drag the handle.

bot.sannysoft.com with stealth on: every check passes
bot.sannysoft.com with stealth off: user agent and webdriver checks fail
stealth off stealth on

Stealth off, four checks fail: the user agent (it says HeadlessChrome/152), navigator.webdriver, and two fingerprint-scanner tests (HEADCHR_UA and CHR_MEMORY). Stealth on: zero failures, and the user agent reads Chrome/152. Captured for this post; you can reproduce it with any Slapify install.

What it actually does

  1. One launch flag. --disable-blink-features=AutomationControlled makes Chromium itself stop reporting automation. navigator.webdriver then returns false natively, with nothing in JavaScript to find.
  2. A clean user agent. If the browser advertises HeadlessChrome and you haven't set one yourself, the browser is relaunched once with the same string minus "Headless". It's done at launch so the HTTP header and navigator.userAgent always agree; patching only the JavaScript value would be a tell of its own.
  3. A script that mostly does nothing. A fingerprint script is injected into every frame before page scripts run. Each patch in it checks first whether the value it would fix actually looks automated, and does nothing otherwise.

Why patch so little

Fingerprinters like CreepJS don't just read values. They check whether values have been tampered with: they compare built-in functions against pristine copies pulled from a fresh iframe, inspect stack traces for wrapped getters, walk property descriptors looking for anything that isn't native code, and ask Web Workers (which injected scripts can't reach) for the same values to see if the main thread disagrees.

So every patch is a detection candidate in its own right. A patch that replaces a value that was already realistic buys nothing and adds a lie to be caught in. Hence the rule: patch nothing that isn't already a tell. Try it: set up a browser environment and see which patches would install.

Which patches install?

Tick what's true of the browser. Stealth checks each condition before patching anything.

PatchInstalled?Why

Two details worth calling out. navigator.permissions.query is deliberately never patched, though most stealth libraries do: the mismatch it used to fix doesn't exist in current Chrome, and wrapping a native method on every page just in case is pure risk. And the Function.prototype.toString cloak, which makes patched getters claim to be native code, is itself detectable. So it's installed only if at least one real patch was, and on a current Chrome that means never.

The numbers

TestStealth offStealth on
bot.sannysoft.com4 failures0 failures
CreepJS headless score100%33%
CreepJS stealth score (detected tampering)0%0%, same fingerprint hash as an unpatched browser
nowsecure.nl (live Cloudflare)passespasses

The 0% stealth score is the point. There is nothing for a tampering detector to find, because on a modern browser stealth mode changes launch flags, not JavaScript.

Where it stops

It is not invisible, and it's worth being precise about why. CreepJS still rates it 33% headless, and what remains is out of reach of anything a page script can do:

  • Client hints. Sec-CH-UA request headers still identify a headless build.
  • Workers. Web Worker scopes see the unpatched environment, by design.
  • Timing and GPU. Rendering and timing behaviour differ from a desktop browser with a real display.

Closing those gaps means going below JavaScript: patching and rebuilding the engine itself, as projects like invisible_playwright do, or running on real hosted browsers via browser.browserbase. For most sites, a clean fingerprint plus a persistent profile with real history is enough. For the rest, Slapify can drive those browsers too.

Use it responsibly. Stealth is for keeping legitimate automation working, such as your own accounts, your own sites, and research you're authorised to do. Respect sites' terms and rate limits.