Tearing down four AI stealth browsers

AI agents created a new market for stealth browsers: patched browsers that hide automation. Here’s what’s in their source code.

Updated June 1, 2026

In the first half of 2026, a handful of GitHub repos went from nothing to tens of thousands of stars in weeks. They were all the same idea: a browser that automation can drive without any website noticing. The demand came from AI agents, which now book, buy, and fill out forms on the open web, and get blocked the moment they look like bots. We read the source of four of these projects to see what they actually hide, and what they give away.

A stealth browser starts as a normal browser engine, usually Chromium or Firefox, then changes what websites can observe. The goal is simple: make an automated browser look like a human-driven one. Bot-detection services compare dozens of signals: automation-protocol traces from tools like Playwright or Puppeteer, fingerprint values such as canvas, WebGL, fonts, screen size, and hardware, and behavioral tells in how the “user” moves, types, and waits. The projects below all close those gaps, just at different layers of the stack. What is new is not the technique but the scale, the speed, and who is paying for it. First, how fast this is growing. Then, the tactics they use to beat detection, and who else gets to use them.

2026 is the year of open-source anti-detect

Cumulative stars by week, 2019–2026. Four projects are highlighted; older or smaller projects are muted for context.

0 5k 10k 15k 20k 2019 2021 2023 2025 CloakBrowser 22,404 Obscura 13,868 undetected 12,656 Camoufox 8,828 puppeteer-extra 7,342 Patchright 3,334
Figure 1 — Each line begins at zero on the project’s launch date. Data pulled from the GitHub stargazers API on May 30, 2026.

Figure 1 is the supply side: open-source projects that try to make automation look like a real browser. The lines are not random. The highlighted projects found attention in a three-month window, while the muted projects show older or smaller tools the market already had. So what changed in 2026? The answer is not in this chart. It is in the next one.

AI agents are funding the stealth-browser boom

Cumulative stars across three groups: agent browser frameworks, open-source stealth browsers, and browser-as-a-service projects.

0 50k 100k 2019 2021 2023 2025 Agent browser frameworks browser-use, Stagehand, Skyvern 140,844 ★ Open-source stealth browsers 9 projects, see Figure 1 78,813 ★ Browser-as-a-service Browserless, Steel 20,335 ★
Figure 2 — Agent browser frameworks are SDKs that turn “tell an LLM to use a browser” into browser commands. They are the customer here. Open-source stealth browsers and browser-as-a-service projects (Browserless, Steel) are two ways to supply the browser those agents run against. The frameworks line takes off in early 2025; the stealth-browser line follows a year later.

Timing matters. Patchright has been around since late 2023 and has grown slowly. CloakBrowser and Obscura launched in the first half of 2026 and reached the top of the chart within weeks. Similar projects, built for similar jobs, grew at very different speeds because they arrived in different markets. The muted lines add context1 Notably undetected-chromedriver, the OG of Selenium-era stealth, which took six years to reach the same star count CloakBrowser hit in three months..

The rest of this piece explains why. The short version: the customer changed.

AI agents changed the buyer

For most of the past decade, stealth browsers served scrapers. The users were researchers, growth teams, gray-hat operators, and small commercial scraping shops. Demand grew with the web, but it stayed bounded by scraping.

In 2024 a different buyer showed up. Browser-using AI agent frameworks — browser-use, Stagehand, Claude Computer Use, OpenAI Operator, Manus, and the long tail of LangChain browser integrations — started reaching production. That changed the question. The agent does not care whether it is detected. The developer building on top of the agent does, because a flagged session means a broken workflow and a lost user. Suddenly, “does this browser pass FingerprintJS, Cloudflare Turnstile, and Kasada?” was a buying question.

The projects tell you who they are for. Camoufox calls itself “The browser built for AI agents.” CloakBrowser has a README section called “Works with AI agents and automation frameworks” and lists drop-in integrations for browser-use, Crawl4AI, Scrapling, Stagehand, and LangChain. Patchright describes itself as the “undetected version of the Playwright Testing and Automation Library.” Testing, not agents. The chart shows what happens when one pitch meets a new buyer and another pitch serves the older market.

The pull was big enough to create an adjacent market. browser-use launched in October 2024 and reached tens of thousands of stars within months. Stagehand, Skyvern, and the rest of the agent-framework group built an audience larger than every open-source stealth browser in Figure 1 combined. And they did it a year earlier. The customer arrived first; the stealth browsers responded later.

Figure 2 compares three groups: the frameworks themselves, open-source stealth browsers, and browser-as-a-service tools like Browserless and Steel2 A fourth category — commercial paid browser-as-a-service like Browserbase, Hyperbrowser, and Anchor — sits in the same column architecturally. Those don’t have GitHub stars to chart, but we measured them separately in last quarter’s leaderboard.. The important part is the timing: frameworks take off in early 2025, about a year before the stealth-browser and browser-as-a-service lines accelerate.

There is another signal in the chart. browser-use slowed through most of 2025, then accelerated again in April and May 2026 — the same months CloakBrowser and Obscura broke out. This was not just a new-project story. Attention moved across the whole agent-browser category.

There is a second beneficiary the projects do not put on their landing pages. A browser that hides a legitimate agent from Cloudflare hides a fraudulent one just as well. The same evasion that keeps a shopping agent from being blocked at checkout is what lets a fraud ring run thousands of fake signups, account takeovers, and card-testing sessions that each look like a different real person on a different real device. AI-agent demand is funding the development; mass fraud inherits the results for free. That is the part of this trend that has not been priced in yet, and it is why the tactics in the rest of this piece matter beyond a developer-tools story.

Four ways to hide automation

Bot detection looks at several surfaces. Three of the projects below focus on one layer each. A fourth, only six weeks old at the time of writing, skips the layered approach and rebuilds the browser from scratch in another language.

DirectionWhat it addressesThe project that took it
Driver How the automation tool steers the browser.CDP (Chrome DevTools Protocol) traffic, init-script delivery, eval stack frames, Runtime.enable timing Patchright
Browser engine What the page can read about the device and browser.Fingerprint values, descriptor shapes, cross-API coherence, every getter that exposes hardware or OS Camoufox
Browser binary The browser program itself, beneath the JavaScript.Compiled-in defaults, network behavior, GPU pool, what is true at the C++ level before any JavaScript runs CloakBrowser
Clean-slate Scrap the existing browser and build a new one.The whole stack, rewritten — different language, different runtime profile, no Chromium or Firefox to inherit from Obscura

None of the first three covers every layer. CloakBrowser even ships Patchright as an optional backend. Through early 2026, the working assumption was that a real stealth stack would combine specialists. Obscura makes the opposite bet: throw out the stack and start over. The market seems willing to consider both.

The four case studies below read each project at HEAD as of late May 2026, in chronological order: Patchright (2023), Camoufox (2024), CloakBrowser (February 2026), and Obscura (April 2026). The order matters.

Spoofing the driver

Vinyzu (pseudonymous) released Patchright in November 2023. The idea is narrow and useful: Playwright leaks automation signals over CDP, and detection scripts can read some of those signals from the page. Patchright patches Playwright’s own driver to remove many of them. The browser underneath stays stock Chromium. The wrapper is the product, and all of it is auditable at Kaliiiiiiiiii-Vinyzu/patchright.

Main trick: remove Runtime.enable. Stock Playwright calls Runtime.enable over CDP at session startup. The renderer then emits Runtime.executionContextCreated and Runtime.consoleAPICalled events and collects full stack frames for thrown exceptions. That state change is observable from the page. The timing attack documented at vanilla.aslushnikov.com shows that accessing Error.stack is measurably slower when Runtime is enabled. Cloudflare, Datadome, and Kasada all check. Patchright removes every Runtime.enable call from the driver and replaces event-driven context discovery with manual probing:

// patchright/driver_patches/framesPatch.ts — Frame._context()
const globalThis = await client._sendMayFail('Runtime.evaluate', {
  expression: "globalThis",
  serializationOptions: { serialization: "idOnly" },
});
const executionContextId =
  parseInt(globalThis.result.objectId.split('.')[1], 10);
this._mainWorld = registerContext(executionContextId, world);

The trick is that the objectId returned by Runtime.evaluate includes the execution context ID, and Patchright parses it out of the string3 The format is "-1234.5678.9012"; the second segment is the contextId. For iframes, Patchright walks DOM.getFrameOwnerDOM.describeNodeDOM.resolveNode to recover the iframe’s content-document context. The format is undocumented and could change between Chromium versions; it works today..

Second trick: rewrite HTML to deliver init scripts. Without Runtime.enable, the normal init-script channel, Page.addScriptToEvaluateOnNewDocument, stops working. Patchright instead enables Fetch.requestPaused from session start, catches every HTML response, parses the <head>, and injects a self-removing <script> tag with a random id. To make this work under strict CSPs, Patchright parses the page’s Content-Security-Policy header (and any <meta>-delivered CSP, including HTML-entity-encoded variants) and appends 'unsafe-eval', 'unsafe-inline', and * to script-src4 This is a significant security weakening of any page Patchright touches. Every protection the original CSP provided — against XSS, against third-party script injection, against framing — is removed. For an automation tool acting as the user this is defensible; in any threat model where the page could host attacker content, it isn’t. The README doesn’t flag this tradeoff explicitly.. That tradeoff is reasonable when the automation is acting as the user. In other threat models, it is much harder to defend.

Third trick: walk closed shadow roots. element.attachShadow({ mode: 'closed' }) creates a shadow root that page JavaScript cannot access. Many stealth libraries stop there, which becomes its own detection signal because anti-bot products can hide traps inside closed shadow roots. Patchright bypasses the DOM API: DOM.describeNode with pierce: true returns all shadow roots regardless of mode, then Patchright walks the tree, calls DOM.resolveNode for each closed root, and runs the user’s selector against every scope. The cost is several CDP round-trips per selector. The value is that queries work in places where they usually fail.

The cost. Removing Console.enable with the same reasoning that motivates removing Runtime.enable means the in-page Console API stops firing events the driver can hear. page.on('console', …) never receives anything. The README admits it openly. Most users don’t notice.

What it does not do. Patchright does not spoof a single fingerprint. The user agent, canvas, WebGL renderer, audio context, and hardware concurrency are all stock. A Patchright session on stock Chromium can pass CDP-detection checks and still fail a solid fingerprint check. Patchright is one piece of a stealth stack, not the whole stack.

The chart shows what happens when a careful project serves the older market. No marketing site, no Discord, and only a couple of proxy vendors paying for README placement. Just a steady climb across thirty-one months while newer projects went vertical around it. Patchright did not change much. The market around it did.

Spoofing the browser

One of the clearest artifacts in this whole space lives in Camoufox’s 1-leak-fixes.patch. It reverses a patch Microsoft applies to its own Firefox fork before shipping Playwright:

// Microsoft’s upstream Firefox patch to dom/base/Navigator.cpp
// (shipped in every Playwright Firefox installation):
bool Navigator::Webdriver() {
  // … checks for Marionette and RemoteAgent running …

  // Playwright is automating the browser,
  // so we should pretend to be a webdriver
  return true;
}
// Camoufox’s 1-leak-fixes.patch undoes it:
bool Navigator::Webdriver() {
  return false;
}

Every Playwright Firefox installation ships with navigator.webdriver === true hardcoded, and the source comment says why: Playwright is automating the browser, so we should pretend to be a webdriver. Camoufox exists, in part, to undo that line and many like it: places where Mozilla, the WebDriver spec, and Microsoft’s automation tooling chose to make automation visible in the browser source. That comment is a clean statement of the conflict stealth browsers are built around.

Camoufox is a Firefox fork by daijro (pseudonymous), launched July 2024 and driven through a Playwright-compatible API. The full source is at daijro/camoufox.

Design choice: use Juggler, not CDP. Firefox does not use CDP. It uses Juggler, a Playwright-owned protocol that lives inside the Firefox source tree as a separate module. That gives Camoufox a lever Patchright does not have. Because Juggler is editable source code, the automation layer can avoid touching the real page DOM. The README says Camoufox gives Playwright an isolated copy of the page to work with while leaving the real page untouched. Inputs go through Firefox’s native user-input handlers — the same path real input takes — so isTrusted is truly true. No flag has to lie.

Main trick: MaskConfig and one JSON blob for everything. Camoufox does not use dozens of separate flags. The whole spoof config is a single JSON object passed to Firefox through the CAMOU_CONFIG environment variable. A C++ header in additions/camoucfg/MaskConfig.hpp reads the env var, parses it, and exposes typed lookups. Every fingerprint-relevant getter in the Firefox source follows the same pattern:

// patches/fingerprint-injection.patch — nsGlobalWindowInner
double nsGlobalWindowInner::GetInnerWidth(ErrorResult& aError) {
  if (auto value = MaskConfig::GetDouble("window.innerWidth"))
    return value.value();
  FORWARD_TO_OUTER_OR_THROW(GetInnerWidthOuter, (aError), aError, 0);
}

That shape repeats across hundreds of getters: check MaskConfig, return the spoofed value if one exists, otherwise fall back to the real value. From the page’s perspective, there is no JavaScript shim to catch. Object.getOwnPropertyDescriptor returns a native descriptor, Function.prototype.toString returns [native code], and workers return the same spoofed values as the main thread. The lie lives in the engine, not in injected script.

Best trick: one process, many fingerprints at once. The same browser process can run multiple Playwright contexts at the same time, each with a different fingerprint: one Windows–Chrome, another macOS–Safari, another Linux–Firefox. They can still share connections, cache, and process resources. Camoufox reuses Firefox’s container-tab partitioning to key spoofed values to a per-context ID. CloakBrowser cannot do this because its fingerprint flags are process-wide. Patchright does not spoof fingerprints at all. This is Camoufox’s unique capability, and the fact that it sits behind a paid package tells you where the value is.

Third trick: 312 real fingerprint presets. In addition to synthesized fingerprints, Camoufox v149+ ships 312 fingerprints scraped from real Firefox traffic (67 macOS, 180 Windows, 65 Linux). The Python library routes them by binary version. This is a practical answer to the population-realism problem: instead of inventing a distribution by hand, sample from traffic that already exists.

The cost. Camoufox cannot spoof Chromium-specific JavaScript-engine behavior. The README is explicit: “Camoufox does not fully support injecting Chromium fingerprints. Some WAFs (such as Interstitial) test for Spidermonkey engine behavior, which is impossible to spoof.” You can spoof navigator.userAgent and claim Chrome, but the engine is still SpiderMonkey. Any test that probes V8-specific behavior, such as object-key ordering, error formatting, or sort stability, can catch the lie. That is the limit of starting from the wrong browser.

What the chart adds. Camoufox climbed for twenty-two months, including one stretch the README explains with unusual candor: “There has been a year gap in maintenance due to a personal situation. Camoufox has gone down in performance due to the base Firefox version and newly discovered fingerprint inconsistencies.” Development later moved under commercial sponsorship, and the chart shows the change: a flat 2025, then a sharp rise when the sponsor became visible. You can date the moment the project became more commercial.

Spoofing the binary

On February 22, 2026, an organization calling itself Cloak-HQ released CloakBrowser: a closed-source custom Chromium binary, pitched on “58 source-level C++ patches,” plus an open-source wrapper that drives it through Playwright. The binary is private. The wrapper is the auditable part, so everything we can verify comes from its flag interface and wrapper source. The repo is at CloakHQ/CloakBrowser.

CloakBrowser goes the opposite direction from Patchright. Patchright fixes the driver and leaves the browser alone. CloakBrowser fixes the browser and leaves the driver mostly alone. The wrapper turns those binary-level features into Python APIs and fills the configuration gaps Playwright leaves open.

The seed-derived noise model. CloakBrowser generates a 5-digit master seed per launch and passes it to the binary as --fingerprint=<seed>. From that seed, the binary derives every randomized surface: canvas noise, WebGL noise, audio noise, GPU vendor and renderer, hardware concurrency, device memory, screen geometry, and font hashes. The goal is not just randomness. It is coherence. Naive stealth tools change canvas but leave audio untouched, which becomes a clustering signal. CloakBrowser keeps the noise internally consistent because it all comes from one seed. The cost: the seed space has 90,000 entries, which caps the entropy of the whole user population5 Pinning a fixed seed is documented as the way to look like a returning visitor. The right answer (one identity per persistent profile, seeds rotated with cookie state) is not enforced by the wrapper; it’s on the operator..

Linux runs as Windows. The wrapper hardcodes --fingerprint-platform=windows on Linux. The reasoning is practical and admits the tradeoff. Windows is the largest population mask. Faking Windows from Mac fails because Mac GPUs and fonts have no clean Windows analog; the README admits “the macOS fingerprint profile has known inconsistencies that aggressive bot detection catches.” Faking Windows from Linux works better because Linux Chrome can use plausible Intel or NVIDIA renderer strings from the binary’s GPU database. The cost is that every Linux CloakBrowser deployment reports as Windows and draws from a finite GPU pool. Coherent per session; clusterable across many sessions.

The wrapper closes Playwright’s gaps. Inline proxy credentials are folded into --proxy-server=http://user:pass@host so Chrome sends Proxy-Authorization on the first request. That avoids Playwright’s CDP-based 407 round-trip, which several proxies and Google explicitly flag. Locale and timezone are set with binary flags (--fingerprint-locale, --fingerprint-timezone) rather than Playwright’s Emulation.setLocaleOverride, because the CDP override does not move the whole locale chain together. Geoip resolves the proxy’s exit IP against a GeoLite2 database and uses the result to set four values: timezone, locale, language, and the WebRTC ICE candidate IP. The wrapper’s own comment calls the WebRTC value a “free bonus.” It is a small detail, but an important instinct: related signals should come from the same source.

The humanize feature reads the DOM through isolated worlds. Before each humanized click, the wrapper checks whether the target is visible, attached, stable, and receiving pointer events. Those checks need DOM reads. The obvious path is page.evaluate, but detection scripts can catch it in two ways: it runs in the main world, where monkey-patched DOM methods can see it, and its Error.stack traces contain a recognizable eval at evaluate :302: frame. CloakBrowser instead does this:

# cloakbrowser/human/__init__.py — _SyncIsolatedWorld
def _create_world(self) -> int:
    cdp = self._ensure_cdp()
    tree = cdp.send("Page.getFrameTree")
    frame_id = tree["frameTree"]["frame"]["id"]
    result = cdp.send("Page.createIsolatedWorld", {
        "frameId": frame_id, "worldName": "", "grantUniveralAccess": True,
    })
    self._context_id = result["executionContextId"]
    return self._context_id

Page.createIsolatedWorld returns a fresh execution context with full DOM access (grantUniveralAccess: true) but keeps it invisible to main-world monkey-patches. It also avoids a main-world evaluate stack frame. The wrapper caches the executionContextId and clears it on navigation. Every DOM read in the humanize feature runs there. From the page’s point of view, no JavaScript ran. The wrapper’s own comment labels the fallback path, regular page.evaluate, as “detectable.” That source comment says more than the README.

The hard constraint. The most candid section of the CloakBrowser README documents a problem it cannot fully solve:

The default storage quota is normalized to ~500 MB — exactly what real Chrome reports in incognito mode, and exactly what BrowserScan’s notPrivate check fires on, costing a documented −10 points. Raising it to ~5000 MB flips the tradeoff: passes BrowserScan, fails FingerprintJS’s tampering detector. The README is explicit that you cannot satisfy both simultaneously.

Two major commercial detection vendors key on opposite sides of the same Web Storage API surface, so CloakBrowser exposes the choice as a flag. There is nowhere to hide it. The constraint appears in troubleshooting, not marketing, because users kept running into it. Most stealth tools do not document limits this clearly because most do not notice them. CloakBrowser noticing it is a point in its favor.

What the chart adds. CloakBrowser launched like a product: landing page, Docker image, multi-language SDKs, and a list of thirty-plus detection services it claims to beat. The chart shows what that polish looked like when the agent-browser market was ready: a Hacker News surge in early May, then a steep climb through the rest of the month. The race against detection vendors is not only technical anymore. It is economic, and CloakBrowser arrived when people were finally paying attention.

A different approach

On April 13, 2026, a developer using the pseudonym h4ckf0r0day shipped Obscura: a headless browser engine written from scratch in Rust, embedding V8 directly, exposing the Chrome DevTools Protocol, and acting as a drop-in for headless Chrome under Puppeteer and Playwright. Three weeks later it had crossed 10,000 stars. The repo is at h4ckf0r0day/obscura. The README leads with operational numbers, not stealth: roughly 6–7× less memory than headless Chrome and effectively instant startup. Stealth is a separate concern behind a separate flag.

Design choice: stealth is opt-in. The other three projects build stealth into the browser or driver. Obscura puts it behind a --stealth CLI flag. By default, the product is a fast, small headless browser, but it gives off the same automation signals as other CDP-driven browsers. The flag switches on a different network stack and a curated tracker blocklist. Obscura’s bet is that stealth is partly a network-layer problem, and that the network layer is easier to replace when you own the browser from the start.

Main trick: TLS handshake impersonation through wreq. Stealth mode routes all outbound HTTP through wreq, a Rust HTTP client whose wreq_util::Emulation::Chrome145 mode reproduces Chrome 145’s TLS Client Hello byte-for-byte: cipher suites, extension ordering, GREASE values, ALPN list, and supported groups. The client is built in crates/obscura-net/src/wreq_client.rs:

// crates/obscura-net/src/wreq_client.rs — stealth path only
let cert_store = wreq::tls::CertStore::default();  // bundled Mozilla roots

let emulation_opts = wreq_util::EmulationOption::builder()
    .emulation(wreq_util::Emulation::Chrome145)
    .emulation_os(wreq_util::EmulationOS::Linux)
    .build();

let builder = wreq::Client::builder()
    .emulation(emulation_opts)
    .cert_store(cert_store)
    .timeout(Duration::from_secs(30))
    .redirect(wreq::redirect::Policy::none());

JA3 and JA4 hashes match real Chrome. Bundled Mozilla root certificates (webpki-root-certs) replace the OS cert store, so the handshake byte sequence stays identical across Linux, macOS, and Windows builds. Chromium itself cannot guarantee that because it uses the OS cert store. None of the other three projects here touch TLS; they inherit Chromium’s network stack. CloakBrowser’s README claims TLS-fingerprint parity with real Chrome “by inheritance,” which is plausible. Obscura makes it explicit, with a code path you can read.

Descriptor introspection barely applies. The classic stealth tell — there is an interactive demo below — is that Object.defineProperty(navigator, 'webdriver', {value: false}) leaves non-native descriptors that Reflect.getOwnPropertyDescriptor from a Worker can catch. Obscura’s V8 runtime has no Chromium-derived Navigator object to patch. The whole navigator surface is built as a plain JavaScript literal in crates/obscura-js/js/bootstrap.js:

// crates/obscura-js/js/bootstrap.js — hand-rolled navigator literal
globalThis.navigator = {
  get userAgent() { return globalThis.__obscura_ua || "Mozilla/5.0 (X11; …)"; },
  language: "en-US", languages: ["en-US","en"], platform: "Linux x86_64",
  hardwareConcurrency: 8, deviceMemory: 8, maxTouchPoints: 0,
  vendor: "Google Inc.", product: "Gecko", productSub: "20030107",
  connection: { effectiveType: "4g", rtt: 50, downlink: 10, saveData: false },
  get webdriver() { return undefined; },
  pdfViewerEnabled: true,
  get plugins() {
    const p = [
      { name: "PDF Viewer", filename: "internal-pdf-viewer", … },
      { name: "Chrome PDF Viewer", … },
      { name: "Chromium PDF Viewer", … },
      { name: "Microsoft Edge PDF Viewer", … },
      { name: "WebKit built-in PDF", … },
    ];
    p.item = (i) => p[i] || null;
    p.namedItem = (name) => p.find(x => x.name === name) || null;
    return p;
  },
  userAgentData: { brands: […], mobile: false, platform: "Linux",
    getHighEntropyValues(hints) { return Promise.resolve({ … }); },
  },
  // … ~150 lines total
};

There is no “real” native binding to compare against, because this V8 runtime has no DOM until the bootstrap script runs. The descriptor is the binding. The plugins array is hand-built to match Chrome’s PDF-viewer list, userAgentData.getHighEntropyValues returns a full Client Hints response, and connection reports plausible 4G values. The whole surface is about 150 lines of JavaScript. That is the inverse of Camoufox and CloakBrowser, which inherit a real browser and then patch every getter that might give it away.

The gap Obscura admits. The bootstrap navigator is static: the same values on every run unless the operator overrides them. The wreq client has per-session randomization because TLS GREASE values vary per handshake, but the in-page surface is constant. A detector that scores the joint distribution of navigator.hardwareConcurrency, deviceMemory, connection.rtt, and the exact plugin list will see every Obscura session report the same values. That is the population-realism problem in its sharpest form. One Obscura session is coherent. A thousand Obscura sessions cluster harder than anything else here. The author has flagged it in issues; the open question is whether randomization lands before detectors fingerprint the cluster.

What Obscura is and isn’t. The memory savings make it practical to run dozens of concurrent sessions on one VM, which is exactly the AI-agent fleet workload. Embedding V8 is not the hard part. Rendering arbitrary modern HTML and CSS without a battle-tested layout engine is. Obscura’s own source already carries a special case for one heavy fingerprinting vendor whose bundle crashes the runtime outright. That is the kind of compatibility surface a from-scratch engine has to grow one site at a time. Whether the premise survives that growth is a 2027 question. For now, the chart says the market is willing to bet on it.

Takeaways

Five patterns show up across the four projects. Together, they describe the open-source stealth-browser playbook as of mid-2026.

1. Descriptor introspection killed init-script overlays. The override creates a data descriptor. The native binding is an accessor. The demo below proves it in your own browser, and Reflect.getOwnPropertyDescriptor from a Worker catches the difference because the init script cannot have run there. playwright-stealth and undetected-chromedriver keep breaking on FingerprintJS for this reason. Two of the 2026 projects went all the way to forking the browser to fix it.

2. Per-API spoofing is not enough. WebGL coherence requires WebGPU coherence. Subgroup sizes (warps on NVIDIA, wavefronts on AMD) leak silicon. Canvas coherence requires audio coherence. A canvas-of-emoji rendered with a missing system font produces an unrecognized hash on Kasada. Seed-based approaches like CloakBrowser and Camoufox address cross-API coherence directly because one seed feeds every randomized surface.

3. CDP traffic is visible to detectors. Runtime.enable timing is measurable on the page. page.evaluate stack frames show up in Error.stack. Page.addScriptToEvaluateOnNewDocument deliveries are observable. page.wait_for_timeout sends CDP commands that reCAPTCHA Enterprise specifically lowers scores for. CloakBrowser’s README tells users to replace page.wait_for_timeout() with time.sleep() in Python. That advice is quiet evidence that CDP traffic detection is now common.

4. Population realism is the hard next problem. Per-session realism is mostly solved across these projects. A single session can read as a real browser because its values come from one seed and its cross-API checks agree. The gaps are at the population level: a 5-digit seed space, a finite GPU pool, documented constants in config.py. A detector that collects sessions over time can learn those distributions and score future sessions against them. Camoufox’s mUserContextId design is the cleanest answer here. CloakBrowser’s seed pinning is a workaround. Obscura’s clean-slate randomizer is still emerging. Patchright does not address the problem.

5. Some gaps cannot be closed. The storage-quota tradeoff is the cleanest example, but it is not the only one. Camoufox cannot hide SpiderMonkey-vs-V8 engine differences. CloakBrowser’s --ignore-gpu-blocklist flag is rare on real Chrome installs, but broken WebGL in Docker is louder. Patchright weakens CSP to deliver init scripts, which is itself a security signal. The race is bounded by what is actually possible to hide. The most serious projects are honest about those bounds.

What comes next

The growth here did not track technical quality. It tracked audience fit: the projects that pitched AI agents grew fastest, regardless of how elegant the underlying work was. That is the tell that this is a demand story, not a research one. The customer pulled the technology forward, and the technology does not care who the customer turns out to be.

The strongest growth signal is who the project says it serves. Camoufox: “the browser built for AI agents.” CloakBrowser: drop-in integrations with browser-use, Stagehand, and LangChain. Obscura: “the headless browser for AI agents and web scraping.” Patchright: “the undetected version of the Playwright Testing and Automation Library.” Three of the four pitch the buyer that arrived in 2024 and brought budget. One pitches the older testing-and-scraping market. The chart is the shape of that difference.

The next phase will be less forgiving. Stealth tools now need product polish, CI discipline, marketing, and sponsorship to keep pace with commercial detection vendors. A one-person stealth project can go quiet when life happens, and the whole category feels it. Detectors will keep fitting distributions across fleets. Stealth tools will need better ways to generate many realistic identities in one process. Both sides are commercial now, not hobbyist.

The chart kept moving while we wrote this. The three-layer frame was already incomplete because Obscura had arrived with a different premise: maybe the stack should be rebuilt, not patched. A fifth direction could appear just as quickly. That is why the date at the top of this page matters. Any claim about this field has a short shelf life, because the field is moving faster than the article can.

The uncomfortable part is the asymmetry. The work that lets an AI agent check out of a store without being blocked is open source, well funded, and improving every week, and it is one install away for anyone running fraud at scale. The customer who paid for stealth wanted a working agent. Everyone else who picks it up gets the same disguise for free, and the defenders on the other side now face mass-produced evasion that used to take a specialist to build.