Camoufox: setup, proxies and hCaptcha.

Camoufox is an open-source Firefox build that gives each Playwright launch a new device identity. What it is, who maintains it, how to set it up in Python, and what to do when a site shows hCaptcha.

What Camoufox is

The Camoufox README calls it “an open source anti-detect browser built for webscraping & AI agents”. It is Firefox with patches: on every launch, a launcher draws a complete device from a model of real-world traffic (operating system, screen, fonts, GPU, voices, timezone), checks that the parts agree, and the browser reports those values from its C++ code. Playwright drives it, and the automation layer runs where the page cannot see it.

It comes in three parts, all from the same GitHub repository:

Camoufox's parts (versions read from GitHub, PyPI and npm on 2 November 2026)
PartInstallLatest versionLicence
The browser (a patched Firefox)camoufox fetchv158.0.1-beta.41 (Firefox 158.0.1), 29 Oct 2026MPL-2.0
Python launcher (camoufox on PyPI)pip install -U "camoufox[geoip]"0.5.9, 1 Nov 2026MIT
Node launcher (camoufox on npm)npm install camoufox playwright-core0.5.9, 1 Nov 2026MIT

The repository had about 13,000 stars on 2 November 2026. The docs site is camoufox.com, and the packages are on PyPI and npm.

Who maintains it, and how current it is

The repository and the PyPI package are published under daijro, who created the project in July 2024. Most of the work now comes from other contributors: of the 173 commits in October 2026, JWriter20 made 126 and coffeegrind123 made 28, and the npm package is published by jwriter20. Releases go out on a twice-monthly train; October 2026 brought the move from Firefox 156 to 158.

The project calls itself unfinished. The README opens with a warning: “This project is under development. It may not be suitable for stable production use.” Its current browser releases carry beta version numbers. Clover Labs, one of the project’s sponsors, keeps a fork that it publishes as cloverlabs-camoufox on PyPI (0.6.0, May 2026); the camoufox package above is the main project’s.

Install it in Python

Shell · pip
pip install -U "camoufox[geoip]"
camoufox fetch

camoufox fetch downloads the browser build the package release is paired with, plus the fingerprint model and, with the [geoip] extra, an IP location database. Skip it and the first launch downloads them instead. camoufox version prints what is installed, and camoufox remove deletes it all. It needs Python 3.10 or newer. The package installs its own Playwright, pinned below 1.63, so you do not run playwright install.

We installed it on 2 November 2026 in a Python 3.13 virtualenv: it pulled Playwright 1.62.0, and camoufox fetch installed browser v158.0.1-beta.41. The same pip install on Python 3.15 failed while building one of its dependencies, zstandard.

Python quick start with Playwright

Camoufox replaces Playwright’s launch call and hands back a normal Playwright Browser:

Python · camoufox 0.5.9
from camoufox.sync_api import Camoufox

with Camoufox(headless=True) as browser:
    page = browser.new_page()
    page.goto("https://example.com")
    print(page.evaluate("navigator.webdriver"))   # False
    print(page.evaluate("navigator.userAgent"))   # a Firefox 158 user agent

We ran it headless twice. Both runs read navigator.webdriver as false. The first launch drew a Linux machine (user agent X11; Linux x86_64; rv:158.0, a 1920×1080 screen), the second a Mac (Macintosh; Intel Mac OS X 10.15, 1512×982), so each launch really is a different device. Stock Playwright Firefox in the same virtualenv read navigator.webdriver as true.

The first launch also printed a LeakWarning: when the drawn identity claims another operating system than the host’s, Camoufox makes canvas and WebGL readback random, which reads as a privacy-hardened Firefox. Pass your host’s OS (os="macos" on a Mac, os="linux" on Linux) to keep stock canvas behaviour, or canvas_noise=False. headless=False is the default and opens a window, and on Linux headless="virtual" runs a headed browser on a hidden Xvfb display.

The async API is the same with AsyncCamoufox:

Python · async
import asyncio
from camoufox.async_api import AsyncCamoufox

async def main():
    async with AsyncCamoufox(headless=True) as browser:
        page = await browser.new_page()
        await page.goto("https://example.com")

asyncio.run(main())

Node quick start

Node · camoufox 0.5.9
// npm install camoufox playwright-core && npx camoufox fetch
import { Camoufox } from "camoufox";

const browser = await Camoufox({ headless: true });
const page = await browser.newPage();
await page.goto("https://example.com");
await browser.close();

It needs Node 22.15 or newer and playwright-core below 1.63. We ran it the same day with playwright-core 1.62.1: same browser build, navigator.webdriver false, a Windows Firefox 158 user agent.

Proxy setup

The README is direct about it: “Camoufox is intended to be used with rotating proxies (preferably residential IPs).” Pass Playwright’s proxy dict and add geoip=True. The launcher then looks up the exit IP through the proxy and sets timezone, locale, geolocation and the WebRTC IP to match it, so the browser does not claim Berlin while its traffic leaves from Dallas. This example is adapted from the Camoufox Python README:

Python · proxy and geoip
from camoufox.sync_api import Camoufox, NewContext

with Camoufox(
    headless=True,
    proxy={"server": "http://proxy.example.com:8080", "username": "user", "password": "pass"},
    geoip=True,
) as browser:
    page = browser.new_page()
    page.goto("https://example.com")

    # A second identity with its own proxy, in the same browser:
    other = NewContext(
        browser,
        proxy={
            "server": "http://proxy2.example.com:8080",
            "username": "user",
            "password": "pass",
        },
    )
    other.new_page().goto("https://example.com")

NewContext gives each context its own identity and, if you pass one, its own proxy, so one browser can carry several. With geoip=True and no proxy, Camoufox keeps IPv6 on only when your machine’s IPv6 address places you where its IPv4 address does; otherwise the browser behaves as if on an IPv4-only network.

One Playwright difference: the isolated world

Camoufox runs Playwright’s evaluate, selectors and init scripts in an isolated copy of the page, so the page never sees them. The DOM is shared, so reading elements and setting a field’s value work as usual. The page’s own JavaScript variables and functions are not visible from there. To reach them, launch with main_world_eval=True and prefix the script with mw::

Python · isolated world vs mw:
with Camoufox(headless=True, main_world_eval=True) as browser:
    page = browser.new_page()
    page.goto("https://your-site.example/")

    # 'undefined': the page's own functions are not visible here
    page.evaluate("typeof window.onSolved")

    # 'function': mw: runs in the page's own world
    page.evaluate("mw:typeof window.onSolved")

That matters for hCaptcha. Many Playwright examples, including ours, finish by calling the widget’s data-callback function with window[name](token). In Camoufox, our test of that call raised “window.onSolved is not a function”; the same call with mw: ran the page’s callback.

What its own docs say it does not do

In the project’s words, as read on 2 November 2026:

  • Not production-ready. “This project is under development. It may not be suitable for stable production use.”
  • Not undetectable. “Camoufox can still be detected. Thousands of values must agree with each other, and anti-bot vendors look for the one that does not.” Site-specific reports are collected in one tracking issue.
  • Firefox only. “Camoufox presents Firefox identities only. Some WAFs test SpiderMonkey engine behaviour, which no amount of spoofing turns into Chromium.”
  • Not your IP. Its leak-debugging chart starts by checking whether the site also flags stock Firefox; if it does, the verdict is “Not the browser: a bad IP or rate limiting”.
  • No CAPTCHA solving. The only CAPTCHA the Python README names is Turnstile, in the disable_coop option, which lets Playwright click the Turnstile checkbox inside its cross-origin frame. Neither the main README nor the Python README mentions hCaptcha.

Does Camoufox get past hCaptcha?

Camoufox changes what your browser says about itself. That is one of several things that decide whether a site’s hCaptcha shows a challenge at all; why am I getting hCaptcha covers the others, starting with the reputation of your IP address.

Once a challenge does appear, Camoufox has nothing more to offer. The site accepts the form only with a valid token in the h-captcha-response field, a P1_ string its server checks with hCaptcha before it lets the request through. Invisible hCaptcha needs that token too, even when no puzzle is shown. A browser, however well disguised, does not produce one.

Get an hCaptcha token in a Camoufox session

NoneCap is an hCaptcha solving API. Keep Camoufox for the rest of your run, and when a page has hCaptcha, send its sitekey and url to NoneCap, get a P1_ token back, put it in the form and submit, or call the widget’s callback through mw:. Failed, cancelled and expired solves are never charged.

Python · Camoufox plus an hCaptcha token
import os
import time
import requests
from camoufox.sync_api import Camoufox

API = "https://api.nonecap.com/v1/solves"
HEADERS = {"Authorization": f"Bearer {os.environ['NONECAP_KEY']}"}

# One sticky proxy session for the browser and the solve.
PROXY = {
    "host": "proxy.example.com",
    "port": 8080,
    "username": "user",
    "password": "pass",
}
CAMOUFOX_PROXY = {
    "server": f"http://{PROXY['host']}:{PROXY['port']}",
    "username": PROXY["username"],
    "password": PROXY["password"],
}

def solve_hcaptcha(sitekey: str, url: str, user_agent: str) -> str:
    r = requests.post(
        API,
        headers=HEADERS,
        params={"wait": 90},
        json={
            "type": "hcaptcha",
            "sitekey": sitekey,
            "url": url,
            "user_agent": user_agent,
            "proxy": PROXY,
        },
        timeout=120,
    )
    r.raise_for_status()
    data = r.json()
    while data["status"] in ("pending", "solving"):
        time.sleep(2)
        data = requests.get(f"{API}/{data['id']}", headers=HEADERS, timeout=30).json()
    if data["status"] != "solved":
        raise RuntimeError(f"solve {data['status']}: {data.get('error')}")
    return data["token"]

with Camoufox(
    headless=True,
    proxy=CAMOUFOX_PROXY,
    geoip=True,
    main_world_eval=True,
) as browser:
    page = browser.new_page()
    page.goto("https://your-site.example/signup")

    widget = page.locator("[data-sitekey]").first
    sitekey = widget.get_attribute("data-sitekey")
    callback = widget.get_attribute("data-callback")
    token = solve_hcaptcha(sitekey, page.url, page.evaluate("navigator.userAgent"))

    page.evaluate(
        """(token) => {
            for (const name of ['h-captcha-response', 'g-recaptcha-response']) {
                const el = document.querySelector(`textarea[name="${name}"]`);
                if (el) el.value = token;
            }
        }""",
        token,
    )
    if callback:
        page.evaluate(f"mw:(token) => window['{callback}'](token)", token)
    else:
        page.click("button[type=submit]")

Two optional fields carry the browser’s context into the solve: user_agent, the user agent of the browser that loaded the page, which the API reference says some sites need for the token to be accepted; and proxy, so the solve leaves from the same address as your Camoufox session. Some sites reject a token submitted from a different address, so use one sticky session for page load, solve and submit; the hCaptcha proxy guide explains when that matters. Leave proxy out and the solve runs from NoneCap’s own pool.

The Playwright guide covers invisible and enterprise (rqdata) sitekeys and how to tell hCaptcha from reCAPTCHA and Turnstile in the DOM, and every field and status is in the API reference. NoneCap solves hCaptcha only; it does not help with reCAPTCHA, Turnstile or other CAPTCHAs.

Camoufox alternatives

If you need Chromium rather than Firefox, the options patch Chrome instead of building a new browser:

  • playwright-stealth injects JavaScript patches into each page of an ordinary Playwright browser. The README’s comparison table sets this against Camoufox: page overrides show up in toString() and are often missed in workers and iframes.
  • Patchright is a patched Playwright driver. By its own README, it “only patches CHROMIUM based browsers. Firefox and Webkit are not supported.”
  • Antidetect profile browsers such as AdsPower are desktop apps with saved profiles rather than libraries; the AdsPower guide covers hCaptcha there.

None of these returns an hCaptcha token either; the solve step above works the same with each of them.

Solve hCaptcha in Playwright →

Last updated November 2026.

Frequently asked

What is Camoufox?
Camoufox is an open-source build of Firefox for web scraping and AI agents. Each launch gets a new, internally consistent device identity (user agent, screen, fonts, WebGL, timezone and more), which the browser reports from its own C++ code rather than from scripts injected into the page. You drive it with Playwright through the camoufox package for Python or Node.
Is Camoufox free and open source?
Yes. The browser patches are MPL-2.0, the licence of the Firefox source they modify, and the Python and Node packages are MIT. The code is on GitHub at daijro/camoufox.
Who maintains Camoufox?
The repository and the PyPI package are published under daijro, who started the project in July 2024. Most of the work in October 2026 was committed by JWriter20 (126 of 173 commits) and coffeegrind123 (28), and the npm package is published by jwriter20. Releases go out on a twice-monthly train.
Does Camoufox work with Playwright?
Yes, Playwright is how you drive it. Camoufox(...) returns an ordinary Playwright Browser, so existing Playwright code works once the launch line changes. The packages pin Playwright below 1.63. One difference: page.evaluate runs in an isolated world, so to call a function the page defined you launch with main_world_eval=True and prefix the script with mw:.
How do I use a proxy with Camoufox?
Pass Playwright’s proxy dict, proxy={"server": ..., "username": ..., "password": ...}, and add geoip=True so timezone, locale, geolocation and the WebRTC IP match the proxy’s exit IP. geoip needs the [geoip] extra. NewContext(browser, proxy=...) gives one context its own proxy.
Is camoufox-js the same as Camoufox for Node?
No. camoufox-js is Apify’s separate “Experimental JS port of Camoufox for Python” (0.13.0, October 2026). The Camoufox project now publishes its own Node package as camoufox on npm.
Does Camoufox solve CAPTCHAs?
No. Camoufox changes what your browser reports about itself; it has no CAPTCHA solver, and its READMEs do not mention hCaptcha. When a site shows hCaptcha, the form still needs a valid token in h-captcha-response. NoneCap returns that token for hCaptcha.
Is it allowed to use Camoufox and a solver on a site?
That depends on the site and on what you do there. Automate where you are allowed to: your own sites, your tests, and services whose terms permit it. NoneCap’s terms rule out fraud, credential stuffing, spam and unauthorised access. This is not legal advice.

Start solving hCaptcha in minutes.

1,300 free credits on signup. Pay per solve, credits never expire, failed solves never charged.