Tearing down four AI stealth browsers

AI agents have created demand for stealth browsers, which are browsers modified to hide automation. We reviewed the source code of four of them.

Read the research
Updated June 1, 2026

In the first half of 2026, several GitHub projects went from launch to tens of thousands of stars within weeks. Each of them is a browser that automation software can drive without websites detecting it. Most of the demand comes from AI agents, which now book travel, make purchases, and fill out forms on the open web, and which are blocked when a site identifies them as bots. We read the source code of four of these projects to document what each one hides and what it still exposes.

A stealth browser starts from a standard browser engine, usually Chromium or Firefox, and changes the values that websites can observe so that an automated session looks like one driven by a person. Bot-detection services compare many signals, which fall into three groups: automation-protocol traces left by tools such as Playwright and Puppeteer, fingerprint values such as canvas, WebGL, fonts, screen size, and hardware, and behavioral signals in how the user moves the pointer, types, and waits. Each project in this article addresses these signals at a different layer of the browser. Most of the techniques predate 2026. This article covers how quickly these projects have grown, the techniques each one uses, and who else benefits from them.

GitHub stars for open-source stealth browsers

Cumulative stars by week, 2019–2026. The four projects covered in this article are highlighted, and older or smaller projects are muted for context.

05k10k15k20k2019202120232025CloakBrowser22,404Obscura13,868undetected12,656Camoufox8,828puppeteer-extra7,342Patchright3,334
Figure 1. Each line starts at zero on the project’s launch date. Data is from the GitHub stargazers API, collected on May 30, 2026.

Figure 1 shows open-source projects that make an automated browser look like an ordinary one. The highlighted projects gained most of their stars within the same three-month period, and the muted lines are older or smaller tools.

GitHub stars by category

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

050k100k2019202120232025Agent browser frameworksbrowser-use, Stagehand, Skyvern140,844 ★Open-source stealth browsers9 projects, see Figure 178,813 ★Browser-as-a-serviceBrowserless, Steel20,335 ★
Figure 2. Agent browser frameworks are SDKs that turn an instruction given to an LLM, such as “use a browser to do this,” into browser commands. In this chart they represent the customer. Open-source stealth browsers and browser-as-a-service projects (Browserless, Steel) are two ways to supply the browser that those agents run in. The frameworks line rises sharply in early 2025, and the stealth-browser line follows about a year later.

Patchright has existed since late 2023 and has grown slowly. CloakBrowser and Obscura launched in the first half of 2026 and reached the top of Figure 1 within weeks1 The clearest example is undetected-chromedriver, the most widely used stealth tool of the Selenium era. It took six years to reach the star count that CloakBrowser reached in three months.. The projects do similar things, and most of the difference in growth comes from who they were built for.

Demand from AI agents

For most of the past decade, stealth browsers were used mainly for web scraping. Their users included researchers, growth teams, gray-hat operators, and small commercial scraping businesses. Demand grew along with the web, but it was limited by the size of the scraping market.

In 2024, a new group of users appeared. Browser-based AI agent frameworks, including browser-use, Stagehand, Claude Computer Use, OpenAI Operator, Manus, and many LangChain browser integrations, began to reach production. Developers building on these frameworks need their sessions to go undetected, because a flagged session breaks the workflow and can cost them a user. Whether a browser passes FingerprintJS, Cloudflare Turnstile, and Kasada became a factor in which browser they chose.

The projects describe their intended users in their own documentation. Camoufox calls itself “The browser built for AI agents.” CloakBrowser’s README has a section titled “Works with AI agents and automation frameworks” that lists integrations for browser-use, Crawl4AI, Scrapling, Stagehand, and LangChain. Patchright describes itself as the “undetected version of the Playwright Testing and Automation Library,” which targets testing. The projects that target agents are the ones that grew fastest in Figure 1.

The agent frameworks grew first. browser-use launched in October 2024 and reached tens of thousands of stars within months, and Stagehand, Skyvern, and the other frameworks together have more stars than all of the stealth browsers in Figure 1. Figure 2 compares the frameworks with open-source stealth browsers and with browser-as-a-service tools such as Browserless and Steel2 A fourth category, commercial browser-as-a-service products such as Browserbase, Hyperbrowser, and Anchor, has the same architecture. These products do not have GitHub stars to chart, but we measured them separately in last quarter’s leaderboard.. The frameworks line rises in early 2025, about a year before the other two.

browser-use growth slowed through most of 2025 and picked up again in April and May 2026, the same months in which CloakBrowser and Obscura grew fastest, so interest rose across the agent-browser category as a whole.

These tools are also useful for fraud. A browser that hides a legitimate agent from Cloudflare hides a fraudulent one equally well, and the evasion that keeps a shopping agent from being blocked at checkout also lets a fraud operation run thousands of fake signups, account takeovers, and card-testing sessions that each appear to come from a different person on a different device. Demand from AI agents funds the development of these projects, and anyone can download them.

Four approaches to hiding automation

Bot detection examines several layers of the browser. Three of the projects in this article each focus on one of those layers. The fourth, which was six weeks old when this article was written, does not patch an existing browser and instead implements a new one from scratch in a different language.

ApproachWhat it changesProject
DriverHow the automation tool controls the browser.CDP (Chrome DevTools Protocol) traffic, init-script delivery, eval stack frames, Runtime.enable timingPatchright
Browser engineWhat the page can read about the device and browser.Fingerprint values, descriptor shapes, cross-API coherence, every getter that exposes hardware or OSCamoufox
Browser binaryThe compiled browser program, below the JavaScript layer.Compiled-in defaults, network behavior, GPU pool, what is true at the C++ level before any JavaScript runsCloakBrowser
New engineThe entire browser, replaced with a new implementation.The whole stack, rewritten in a different language with a different runtime profile and no Chromium or Firefox codeObscura

None of the first three projects covers every layer, and CloakBrowser ships Patchright as an optional backend, so a complete setup usually combines more than one of them. Obscura replaces the whole stack instead.

The sections below describe each project as of its main branch in late May 2026, in order of release: Patchright (2023), Camoufox (2024), CloakBrowser (February 2026), and Obscura (April 2026).

Patchright: patching the driver

Patchright was released in November 2023 by a developer who uses the pseudonym Vinyzu. Playwright leaks automation signals over CDP, and detection scripts can read some of those signals from the page. Patchright modifies Playwright’s driver to remove many of them and leaves the browser as unmodified Chromium. The source is at Kaliiiiiiiiii-Vinyzu/patchright. Patchright has no marketing site or Discord server, a few proxy vendors pay for placement in its README, and its star count has grown steadily over thirty-one months.

Removing Runtime.enable

Standard Playwright sends Runtime.enable over CDP when a session starts. After that call, the renderer emits Runtime.executionContextCreated and Runtime.consoleAPICalled events and collects full stack frames for thrown exceptions, and this change in state can be observed from the page. The timing attack documented at vanilla.aslushnikov.com shows that reading Error.stack is measurably slower when the Runtime domain is enabled, and Cloudflare, Datadome, and Kasada all check for it. Patchright removes every Runtime.enable call from the driver and replaces event-based context discovery with explicit 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 objectId that Runtime.evaluate returns contains the execution context ID, so Patchright gets the ID by parsing that string3 The format is "-1234.5678.9012", and the second segment is the context ID. For iframes, Patchright calls DOM.getFrameOwner, then DOM.describeNode, then DOM.resolveNode to find the context for the iframe’s content document. The format is undocumented and could change between Chromium versions, but it works as of this writing..

Delivering init scripts by rewriting HTML

Without Runtime.enable, the usual channel for init scripts, Page.addScriptToEvaluateOnNewDocument, no longer works. Instead, Patchright enables Fetch.requestPaused at the start of the session, intercepts every HTML response, parses the <head>, and inserts a <script> tag with a random ID that removes itself after it runs. To make this work on pages with a strict content security policy, Patchright parses the page’s Content-Security-Policy header, along with any CSP delivered in a <meta> tag (including HTML-entity-encoded variants), and appends 'unsafe-eval', 'unsafe-inline', and * to script-src4 This change significantly weakens the security of every page that Patchright loads, because the protection the original CSP provided against XSS and third-party script injection no longer applies. The tradeoff is defensible for an automation tool that acts as the user, but not in any threat model where the page could contain attacker-controlled content. The README does not describe this tradeoff explicitly..

Querying closed shadow roots

Calling element.attachShadow({ mode: 'closed' }) creates a shadow root that page JavaScript cannot access. Many stealth libraries cannot query inside closed shadow roots either, and that limitation becomes a detection signal because anti-bot products can place traps inside them. Patchright works around the DOM API instead: DOM.describeNode with pierce: true returns every shadow root regardless of mode, and Patchright then walks the tree, calls DOM.resolveNode for each closed root, and runs the user’s selector in every scope. This takes several CDP round trips per selector.

Limitations

Patchright also removes Console.enable, for the same reason it removes Runtime.enable. As a result, console calls in the page no longer produce events that the driver receives, and page.on('console', …) never fires. The README documents this limitation.

Patchright does not spoof any fingerprint values. The user agent, canvas, WebGL renderer, audio context, and hardware concurrency are all unmodified. A Patchright session on standard Chromium can pass CDP detection checks and still fail a thorough fingerprint check, because Patchright covers only the driver layer.

Camoufox: patching the engine

Camoufox is a Firefox fork created by a developer who uses the pseudonym daijro. It launched in July 2024 and is controlled through a Playwright-compatible API, and its source is at daijro/camoufox. Its star count grew for twenty-two months with a flat stretch through 2025, which the README attributes to a maintenance gap: “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 growth rose sharply after that.

Camoufox’s 1-leak-fixes.patch reverses a patch that Microsoft applies to its own Firefox fork before shipping it with 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 hardcodes navigator.webdriver to true, and the source comment gives the reason: Playwright is automating the browser, so we should pretend to be a webdriver. Camoufox reverses this line and others like it, where Mozilla, the WebDriver specification, or Microsoft’s automation tooling made automation visible to pages.

Using Juggler instead of CDP

Firefox does not use CDP. Playwright controls Firefox through Juggler, a Playwright-owned protocol implemented as a separate module inside the Firefox source tree. This gives Camoufox an option that Patchright does not have: because Juggler is part of the source that Camoufox builds, Camoufox can change the automation layer so that it does not touch the real page DOM. According to the README, Camoufox gives Playwright an isolated copy of the page and leaves the real page untouched. Input events go through Firefox’s native user-input handlers, which is the same path that real input takes, so isTrusted is genuinely true without any value being overridden.

Configuring fingerprints with MaskConfig

Camoufox does not use a large set of separate flags. The complete spoofing configuration is a single JSON object that is passed to Firefox through the CAMOU_CONFIG environment variable. A C++ header, additions/camoucfg/MaskConfig.hpp, reads the variable, parses it, and exposes typed lookups. Every fingerprint-related 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);
}

Each getter checks MaskConfig, returns the spoofed value if one is set, and otherwise returns the real value. From the page’s point of view, there is no JavaScript shim to detect: Object.getOwnPropertyDescriptor returns a native descriptor, Function.prototype.toString returns [native code], and workers report the same spoofed values as the main thread, because the spoofing happens in the engine rather than in injected script.

Running multiple fingerprints in one process

A single Camoufox process can run several Playwright contexts at once, each with a different fingerprint, such as Windows with Chrome, macOS with Safari, and Linux with Firefox. The contexts can still share connections, cache, and process resources. Camoufox reuses Firefox’s container-tab partitioning to associate spoofed values with a per-context ID. CloakBrowser cannot do this because its fingerprint flags apply to the whole process, and Patchright does not spoof fingerprints at all. No other project in this article offers this, and Camoufox sells it as a paid package.

Fingerprint presets from real traffic

In addition to generated fingerprints, Camoufox v149 and later ship 312 fingerprints collected from real Firefox traffic: 67 from macOS, 180 from Windows, and 65 from Linux. The Python library selects presets according to the binary version. This addresses population realism, which is the problem of making a large number of sessions resemble a real population of users, by sampling from existing traffic instead of constructing a distribution by hand.

Limitations

Camoufox cannot spoof behavior that is specific to Chromium’s JavaScript engine. The README states: “Camoufox does not fully support injecting Chromium fingerprints. Some WAFs (such as Interstitial) test for Spidermonkey engine behavior, which is impossible to spoof.” A Camoufox session can set navigator.userAgent to a Chrome value, but the engine is still SpiderMonkey, and any test that probes V8-specific behavior, such as object key ordering, error message formatting, or sort stability, can detect the mismatch.

CloakBrowser: patching the binary

On February 22, 2026, an organization named Cloak-HQ released CloakBrowser. It consists of a closed-source custom Chromium binary, advertised as containing “58 source-level C++ patches,” and an open-source wrapper that controls the binary through Playwright. Because the binary is private, everything we could verify comes from the wrapper’s flag interface and source code. The repository is at CloakHQ/CloakBrowser.

CloakBrowser launched with a landing page, a Docker image, SDKs for several languages, and a list of more than thirty detection services that it claims to bypass. Its star count jumped after a Hacker News post in early May 2026 and kept climbing through the rest of the month.

CloakBrowser takes the opposite approach from Patchright. Patchright modifies the driver and leaves the browser unchanged, while CloakBrowser modifies the browser and leaves the driver mostly unchanged. The wrapper exposes the binary’s features as Python APIs and fills configuration gaps that Playwright leaves open.

Seed-derived noise

CloakBrowser generates a five-digit master seed for each launch and passes it to the binary as --fingerprint=<seed>. The binary derives every randomized value from that seed, including canvas noise, WebGL noise, audio noise, GPU vendor and renderer, hardware concurrency, device memory, screen geometry, and font hashes. Deriving everything from one seed keeps the values consistent with each other. Simpler stealth tools add noise to canvas but leave audio unchanged, and that mismatch can be used to cluster their sessions. The seed space has only 90,000 values, which limits the entropy available across all users5 The documentation recommends pinning a fixed seed to appear as a returning visitor. The better approach, which is one identity per persistent profile with seeds rotated together with cookie state, is not enforced by the wrapper and is left to the operator..

Reporting Linux as Windows

On Linux, the wrapper hardcodes --fingerprint-platform=windows, because Windows has the largest population of users to blend into. Presenting as Windows from macOS does not work well because Mac GPUs and fonts have no close Windows equivalents, and the README notes that “the macOS fingerprint profile has known inconsistencies that aggressive bot detection catches.” Presenting as Windows from Linux works better because Linux Chrome can report plausible Intel or NVIDIA renderer strings from the binary’s GPU database. As a result, every Linux deployment of CloakBrowser reports Windows and draws from a finite pool of GPUs, so each session is coherent on its own but a large group of sessions can be clustered.

Filling gaps in Playwright

The wrapper puts inline proxy credentials into --proxy-server=http://user:pass@host so that Chrome sends Proxy-Authorization with the first request. This avoids the CDP-based 407 round trip that Playwright otherwise performs, which several proxy providers and Google flag. Locale and timezone are set with binary flags (--fingerprint-locale and --fingerprint-timezone) instead of Playwright’s Emulation.setLocaleOverride, because the CDP override does not update every locale-related value together. The geoip option looks up the proxy’s exit IP in a GeoLite2 database and uses the result to set four values: timezone, locale, language, and the IP address in WebRTC ICE candidates. The wrapper’s source comment describes the WebRTC value as a “free bonus.”

Reading the DOM from isolated worlds

Before each humanized click, the wrapper checks that the target element is visible, attached, stable, and able to receive pointer events. These checks require reading the DOM. The obvious way to do that is page.evaluate, but detection scripts can identify it in two ways: it runs in the main world, where monkey-patched DOM methods can observe it, and its Error.stack traces contain a recognizable eval at evaluate :302: frame. CloakBrowser uses the following approach instead:

# 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 new execution context that has full DOM access (grantUniveralAccess: true) but is not affected by monkey-patches in the main world, and it does not produce a main-world evaluate stack frame. The wrapper caches the executionContextId, clears it on navigation, and runs every DOM read for the humanize feature in this context, so from the page’s point of view no JavaScript ran. The wrapper’s source labels the fallback path, a regular page.evaluate, as “detectable.”

The storage quota tradeoff

The CloakBrowser README describes one problem that it cannot fully solve:

By default, the storage quota is normalized to about 500 MB. That is the value real Chrome reports in incognito mode, and it is also the value that triggers BrowserScan’s notPrivate check, which costs a documented 10 points. Raising the quota to about 5000 MB reverses the tradeoff, so the browser passes BrowserScan but fails FingerprintJS’s tampering detector. The README states that you cannot satisfy both at the same time.

Two commercial detection vendors check opposite sides of the same Web Storage API value, so CloakBrowser exposes the choice as a flag. The tradeoff is documented in the troubleshooting section of the README.

Obscura: a new browser engine

On April 13, 2026, a developer using the pseudonym h4ckf0r0day released Obscura, a headless browser engine written from scratch in Rust. Obscura embeds V8 directly, exposes the Chrome DevTools Protocol, and can replace headless Chrome under Puppeteer and Playwright. It passed 10,000 stars three weeks after release. The repository is at h4ckf0r0day/obscura. The README leads with performance: roughly 6–7× less memory than headless Chrome and near-instant startup.

Stealth as an opt-in mode

The other three projects build stealth into the browser or the driver, but Obscura puts it behind a --stealth command-line flag. By default, Obscura is a small, fast headless browser that exposes the same automation signals as other CDP-controlled browsers. The flag enables a different network stack and a curated tracker blocklist. Obscura’s design assumes that stealth is partly a network-layer problem, and that the network layer is easier to replace when you control the entire browser.

TLS impersonation with wreq

In stealth mode, all outbound HTTP traffic goes through wreq, a Rust HTTP client. Its wreq_util::Emulation::Chrome145 mode reproduces the TLS Client Hello of Chrome 145 byte for byte, including cipher suites, extension order, GREASE values, the 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());

The resulting JA3 and JA4 hashes match those of real Chrome. Obscura bundles Mozilla’s root certificates (webpki-root-certs) instead of using the operating system’s certificate store, so the handshake bytes are identical on Linux, macOS, and Windows. Chromium cannot guarantee this, because it uses the operating system’s store. None of the other three projects modify TLS, and each one uses the network stack of the browser it is built on. CloakBrowser’s README claims TLS fingerprint parity with real Chrome “by inheritance,” and Obscura implements it explicitly in its own client code.

Descriptor checks

A well-known weakness of stealth tools, shown in the interactive example later in this article, is that Object.defineProperty(navigator, 'webdriver', {value: false}) creates a non-native descriptor that Reflect.getOwnPropertyDescriptor can detect from a Worker. This check mostly does not apply to Obscura. Its V8 runtime has no Chromium Navigator object to patch, and the entire navigator object is defined as a plain JavaScript object 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 native binding to compare against, because this V8 runtime has no DOM until the bootstrap script runs, so the JavaScript definition is the only binding that exists. The plugins array is written by hand to match Chrome’s list of PDF viewers, userAgentData.getHighEntropyValues returns a complete Client Hints response, and connection reports plausible 4G values. The whole surface is about 150 lines of JavaScript. Camoufox and CloakBrowser, by comparison, start from a real browser and patch each getter that might reveal automation.

Limitations

The bootstrap navigator values are static, so every run reports the same values unless the operator overrides them. The wreq client varies some values per session, because TLS GREASE values change with each handshake, but the in-page values do not change. A detector that scores the combination of navigator.hardwareConcurrency, deviceMemory, connection.rtt, and the exact plugin list will see identical values in every Obscura session. A single Obscura session is coherent, but Obscura sessions as a group cluster more tightly than sessions from any other project in this article. The author has acknowledged the issue in the project’s issue tracker.

Scope

Obscura’s lower memory use makes it practical to run dozens of concurrent sessions on one virtual machine, which is the typical workload for a fleet of AI agents. Embedding V8 is the simpler part of the project, and rendering arbitrary modern HTML and CSS without a mature layout engine is much harder. Obscura’s source already includes a special case for one fingerprinting vendor whose script crashes the runtime, and a from-scratch engine has to add compatibility fixes like this one site at a time.

Common patterns

The following patterns appear across the four projects.

Descriptor checks defeat init-script overrides

An override applied by an init script creates a data descriptor, while the native binding is an accessor. The example below shows the difference in your own browser. Calling Reflect.getOwnPropertyDescriptor from a Worker also exposes the difference, because the init script does not run in the Worker. This is why playwright-stealth and undetected-chromedriver continue to fail FingerprintJS checks, and why Camoufox, CloakBrowser, and Obscura change the browser itself instead of injecting scripts.

Spoofing individual APIs is not sufficient

Spoofed WebGL values need to be consistent with WebGPU values, and subgroup sizes, which are called warps on NVIDIA hardware and wavefronts on AMD hardware, reveal the actual GPU. Spoofed canvas values need to be consistent with audio values, and an emoji rendered to canvas with a missing system font produces a hash that Kasada does not recognize. Seed-based designs such as those in CloakBrowser and Camoufox address consistency across APIs directly, because a single seed determines every randomized value.

Detectors can observe CDP traffic

Several CDP behaviors can be observed from the page. The timing effect of Runtime.enable is measurable, stack frames from page.evaluate appear in Error.stack, and scripts delivered through Page.addScriptToEvaluateOnNewDocument can be detected. In addition, page.wait_for_timeout sends CDP commands, and reCAPTCHA Enterprise lowers scores when it sees them. CloakBrowser’s README advises Python users to replace page.wait_for_timeout() with time.sleep(), which indicates that detection of CDP traffic is now common.

Population realism is the next problem

Across these projects, realism for a single session is largely solved. A session can pass as a real browser because its values come from one seed and its cross-API checks agree. The remaining gaps are at the population level, such as the five-digit seed space, the finite GPU pool, and the fixed constants in config.py. A detector that collects sessions over time can learn these distributions and score new sessions against them. Camoufox’s mUserContextId design is the most complete answer to this problem. CloakBrowser’s seed pinning is a workaround, Obscura has not yet added randomization for its in-page values, and Patchright does not address the problem.

Some gaps cannot be closed

The storage quota tradeoff is the clearest example, but there are others. Camoufox cannot hide the differences between SpiderMonkey and V8. CloakBrowser’s --ignore-gpu-blocklist flag is rare on real Chrome installations, although broken WebGL in Docker would be a stronger signal. Patchright weakens CSP to deliver init scripts, and the weakened policy is itself a signal.

Outlook

The projects that grew fastest are the ones that target AI agents. Camoufox describes itself as “the browser built for AI agents,” CloakBrowser offers integrations with browser-use, Stagehand, and LangChain, and Obscura describes itself as “the headless browser for AI agents and web scraping.” Patchright, which describes itself as “the undetected version of the Playwright Testing and Automation Library,” is the only one of the four that targets the older testing and scraping market, and it has the fewest stars.

Detection is likely to shift toward the population-level gaps described in the previous section, because those are the hardest for any one project to close. Anyone can download these projects, including fraud operations, so evasion techniques that once took a specialist to build are now available as maintained packages.