by jo-inc
Stealth headless browser for AI agents — bypass Cloudflare, bot detection, and anti-scraping. Drop-in Puppeteer/Playwright replacement.
# Add to your Claude Code skills
git clone https://github.com/jo-inc/camofox-browserLast scanned: 4/26/2026
{
"issues": [],
"status": "PASSED",
"scannedAt": "2026-04-26T06:06:54.524Z",
"semgrepRan": false,
"npmAuditRan": true,
"pipAuditRan": true
}Built by the team behind jo — a personal AI agent that runs half on your Mac, half on a dedicated cloud machine just for you — with zero maintenance needed. Available on macOS, Telegram, WhatsApp, and email. Try the beta free →
git clone https://github.com/jo-inc/camofox-browser && cd camofox-browser
npm install && npm start
# → http://localhost:9377
AI agents need to browse the real web. Playwright gets blocked. Headless Chrome gets fingerprinted. Stealth plugins become the fingerprint.
Camoufox patches Firefox at the C++ implementation level - navigator.hardwareConcurrency, WebGL renderers, AudioContext, screen geometry, WebRTC - all spoofed before JavaScript ever sees them. No shims, no wrappers, no tells.
This project wraps that engine in a REST API built for agents: accessibility snapshots instead of bloated HTML, stable element refs for clicking, and search macros for common sites.
e1, e2, e3 identifiers for reliable interaction@google_search, @youtube_search, @amazon_search, @reddit_subreddit, and 10 more<img> src/alt and optionally return inline data URLs/openapi.json and interactive docs at /docsPOST /tabs/:tabId/extract with a JSON Schema that maps properties to snapshot refs via x-refCAMOFOX_CRASH_REPORT_ENABLED=false.| Dependency | Purpose | Install |
|-----------|---------|---------|
| yt-dlp | YouTube transcript extraction (fast path) | pip install yt-dlp or brew install yt-dlp |
The Docker image includes yt-dlp. For local dev, install it for the /youtube/transcript endpoint. Without it, the endpoint falls back to a slower browser-based method.
openclaw plugins install @askjo/camofox-browser
Tools: camofox_create_tab · camofox_snapshot · camofox_click · camofox_type · camofox_navigate · camofox_scroll · camofox_screenshot · camofox_close_tab · camofox_list_tabs · camofox_import_cookies
git clone https://github.com/jo-inc/camofox-browser
cd camofox-browser
npm install
npm start # downloads Camoufox on first run (~300MB)
Default port is 9377. See Environment Variables for all options.
The included Makefile auto-detects your CPU architecture and pre-downloads Camoufox + yt-dlp binaries outside the Docker build, so rebuilds are fast (~30s vs ~3min).
# Build and start (auto-detects arch: aarch64 on M1/M2, x86_64 on Intel)
make up
# Stop and remove the container
make down
# Force a clean rebuild (e.g. after upgrading VERSION/RELEASE)
make reset
# Just download binaries (without building)
make fetch
# Override arch or version explicitly
make up ARCH=x86_64
make up VERSION=135.0.1 RELEASE=beta.24
⚠️ Do not run
docker builddirectly. The Dockerfile uses bind mounts to pull pre-downloaded binaries fromdist/. Always usemake up(ormake fetchthenmake build) — it downloads the binaries first.
railway.toml is included. For Fly.io or other remote CI, you'll need a Dockerfile that downloads binaries at build time instead of using bind mounts — see jo-browser for an example.
Import cookies from your browser into Camoufox to skip interactive login on sites like LinkedIn, Amazon, etc.
1. Generate a secret key:
# macOS / Linux
openssl rand -hex 32
2. Set the environment variable before starting OpenClaw:
export CAMOFOX_API_KEY="your-generated-key"
openclaw start
The same key is used by both the plugin (to authenticate requests) and the server (to verify them). Both run from the same environment — set it once.
Why an env var? The key is a secret. Plugin config in
openclaw.jsonis stored in plaintext, so secrets don't belong there. SetCAMOFOX_API_KEYin your shell profile, systemd unit, Docker env, or Fly.io secrets.
Cookie import is disabled by default. If
CAMOFOX_API_KEYis not set, the server rejects all cookie requests with 403.
3. Export cookies from your browser:
Install a browser extension that exports Netscape-format cookie files (e.g., "cookies.txt" for Chrome/Firefox). Export the cookies for the site you want to authenticate.
4. Place the cookie file:
mkdir -p ~/.camofox/cookies
cp ~/Downloads/linkedin_cookies.txt ~/.camofox/cookies/linkedin.txt
The default directory is ~/.camofox/cookies/. Override with CAMOFOX_COOKIES_DIR.
5. Ask your agent to import them:
Import my LinkedIn cookies from linkedin.txt
The agent calls camofox_import_cookies → reads the file → POSTs to the server with the Bearer token → cookies are injected into the browser session. Subsequent camofox_create_tab calls to linkedin.com will be authenticated.
~/.camofox/cookies/linkedin.txt (Netscape format, on disk)
│
▼
camofox_import_cookies tool (parses file, filters by domain)
│
▼ POST /sessions/:userId/cookies
│ Authorization: Bearer <CAMOFOX_API_KEY>
│ Body: { cookies: [Playwright cookie objects] }
▼
camofox server (validates, sanitizes, injects)
│
▼ context.addCookies(...)
│
Camoufox browser session (authenticated browsing)
cookiesPath is resolved relative to the cookies directory — path traversal outside it is blockedBy default, camofox persists each user's cookies and localStorage to ~/.camofox/profiles/. Sessions survive browser restarts — log in once (via cookies or VNC), and subsequent sessions restore the authenticated state automatically.
~/.camofox/
├── cookies/ # Bootstrap cookie files (Netscape format)
└── profiles/ # Persisted session state (auto-managed)
└── <hashed-userId>/
└── storage_state.json
Override the directory with CAMOFOX_PROFILE_DIR or set "profileDir" in the persistence plugin config. To disable persistence, set "persistence": { "enabled": false } in camofox.config.json.
Capture a Playwright trace of every action in a session: page screenshots, DOM snapshots, network requests, and console output. Output is a single .zip file you can open in Playwright's built-in Trace Viewer.
Opt-in per session by passing trace: true when opening the first tab:
curl -X POST http://localhost:9377/tabs \
-H 'Content-Type: application/json' \
-d '{"userId":"agent1","sessionKey":"task1","url":"https://example.com","trace":true}'
The trace is written when the session closes. Close
No comments yet. Be the first to share your thoughts!