camofox-browser

agent
Security Audit
Fail
Health Pass
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Community trust — 3059 GitHub stars
Code Fail
  • process.env — Environment variable access in jest.config.cjs
  • process.env — Environment variable access in jest.config.e2e.cjs
  • os.homedir — User home directory access in lib/config.js
  • process.env — Environment variable access in lib/config.js
  • network request — Outbound network request in lib/images.js
Permissions Pass
  • Permissions — No dangerous permissions requested
Purpose
This agent acts as a stealthy, headless web browser designed specifically for AI. It uses a heavily modified Firefox engine to bypass anti-bot systems like Cloudflare, and provides a token-efficient API for web scraping and automated interaction.

Security Assessment
Overall Risk: Medium. The tool accesses the user's home directory and reads environment variables to manage configurations, such as importing cookies or setting up proxies. It makes outbound network requests, which is expected behavior for a browser, but includes a note that one request occurs in an image processing library. It does not request explicitly dangerous system permissions, and no hardcoded secrets were detected. Developers should be aware that tools designed to spoof fingerprints and bypass security controls can sometimes operate in ethical gray areas depending on their use case.

Quality Assessment
The project is actively maintained, with its most recent push happening today. It has garnered over 3,000 GitHub stars, indicating a strong level of community trust and adoption. Furthermore, the repository is transparent and properly licensed under the MIT license.

Verdict
Use with caution — it is a highly capable and well-maintained project, but its access to the home directory and inherent design to bypass web security controls require careful implementation.
SUMMARY

Stealth headless browser for AI agents — bypass Cloudflare, bot detection, and anti-scraping. Drop-in Puppeteer/Playwright replacement.

README.md
camofox-browser

camofox-browser

Anti-detection browser server for AI agents, powered by Camoufox

Build License Camoufox Docker

Standing on the mighty shoulders of Camoufox - a Firefox fork with fingerprint spoofing at the C++ level.

The same engine behind Jo — an AI assistant that doesn't need you to babysit it. Runs half on your Mac, half on a dedicated cloud machine that only you use. Available on macOS, Telegram, and WhatsApp. Try the beta free →


git clone https://github.com/jo-inc/camofox-browser && cd camofox-browser
npm install && npm start
# → http://localhost:9377

Why

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.

Features

  • C++ Anti-Detection - bypasses Google, Cloudflare, and most bot detection
  • Element Refs - stable e1, e2, e3 identifiers for reliable interaction
  • Token-Efficient - accessibility snapshots are ~90% smaller than raw HTML
  • Runs on Anything - lazy browser launch + idle shutdown keeps memory at ~40MB when idle. Designed to share a box with the rest of your stack — Raspberry Pi, $5 VPS, shared Railway infra.
  • Session Isolation - separate cookies/storage per user
  • Cookie Import - inject Netscape-format cookie files for authenticated browsing
  • Proxy + GeoIP - route traffic through residential proxies with automatic locale/timezone
  • Structured Logging - JSON log lines with request IDs for production observability
  • YouTube Transcripts - extract captions from any YouTube video via yt-dlp, no API key needed
  • Search Macros - @google_search, @youtube_search, @amazon_search, @reddit_subreddit, and 10 more
  • Snapshot Screenshots - include a base64 PNG screenshot alongside the accessibility snapshot
  • Large Page Handling - automatic snapshot truncation with offset-based pagination
  • Download Capture - capture browser downloads and fetch them via API (optional inline base64)
  • DOM Image Extraction - list <img> src/alt and optionally return inline data URLs
  • Deploy Anywhere - Docker, Fly.io, Railway
  • VNC Interactive Login - log into sites visually via noVNC, export storage state for agent reuse
  • OpenAPI Docs - auto-generated spec at /openapi.json and interactive docs at /docs
  • Structured Extract - POST /tabs/:tabId/extract with a JSON Schema that maps properties to snapshot refs via x-ref
  • Session Tracing - opt-in per-session Playwright trace capture (screenshots + DOM snapshots + network) with API endpoints to list, fetch, and delete trace zips

Optional Dependencies

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.

Quick Start

OpenClaw Plugin

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

Standalone

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.

Docker

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 build directly. The Dockerfile uses bind mounts to pull pre-downloaded binaries from dist/. Always use make up (or make fetch then make build) — it downloads the binaries first.

Fly.io / Railway

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.

Usage

Cookie Import

Import cookies from your browser into Camoufox to skip interactive login on sites like LinkedIn, Amazon, etc.

Setup

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.json is stored in plaintext, so secrets don't belong there. Set CAMOFOX_API_KEY in your shell profile, systemd unit, Docker env, or Fly.io secrets.

Cookie import is disabled by default. If CAMOFOX_API_KEY is 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.

How it works

~/.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 blocked
  • Max 500 cookies per request, 5MB file size limit
  • Cookie objects are sanitized to an allowlist of Playwright fields

Session Persistence

By 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.

Session Tracing

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 the session to flush it, then list, fetch, and view:

# Close the session to flush the trace
curl -X DELETE http://localhost:9377/sessions/agent1

# List trace files
curl http://localhost:9377/sessions/agent1/traces
# {"traces":[{"filename":"trace-2026-04-18T04-05-00-...zip","sizeBytes":42810,"createdAt":...}]}

# Download (Content-Type: application/zip)
curl http://localhost:9377/sessions/agent1/traces/trace-2026-04-18T04-05-00-abc.zip > session.zip

# View it in Playwright's Trace Viewer
npx playwright show-trace session.zip

# Delete
curl -X DELETE http://localhost:9377/sessions/agent1/traces/trace-2026-04-18T04-05-00-abc.zip

Why traces instead of video: Camoufox is Firefox-based, and Playwright's recordVideo is Chromium-only. Traces work on Firefox and give you more than video (network + DOM + console + screenshots).

Tracing cannot be toggled on an existing session. DELETE /sessions/:userId first if you need to change the flag.

Storage defaults to ~/.camofox/traces/<hashed-userId>/ and is swept on server startup:

  • CAMOFOX_TRACES_DIR - base directory (default: ~/.camofox/traces)
  • CAMOFOX_TRACES_MAX_BYTES - max size per trace, removed at next startup if exceeded (default: 50MB)
  • CAMOFOX_TRACES_TTL_HOURS - traces older than this are removed at next startup (default: 24)

Standalone server usage

curl -X POST http://localhost:9377/sessions/agent1/cookies \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_CAMOFOX_API_KEY' \
  -d '{"cookies":[{"name":"foo","value":"bar","domain":"example.com","path":"/","expires":-1,"httpOnly":false,"secure":false}]}'

Docker / Fly.io

docker run -p 9377:9377 \
  -e CAMOFOX_API_KEY="your-generated-key" \
  -v ~/.camofox/cookies:/home/node/.camofox/cookies:ro \
  camofox-browser

For Fly.io:

fly secrets set CAMOFOX_API_KEY="your-generated-key"

Proxy + GeoIP

Route all browser traffic through a proxy with automatic locale, timezone, and geolocation derived from the proxy's IP address via Camoufox's built-in GeoIP.

Simple proxy (single endpoint):

export PROXY_HOST=166.88.179.132
export PROXY_PORT=46040
export PROXY_USERNAME=myuser
export PROXY_PASSWORD=mypass
npm start

Backconnect proxy (rotating sticky sessions):

For providers like Decodo, Bright Data, or Oxylabs that offer a single gateway endpoint with session-based sticky IPs:

export PROXY_STRATEGY=backconnect
export PROXY_BACKCONNECT_HOST=gate.provider.com
export PROXY_BACKCONNECT_PORT=7000
export PROXY_USERNAME=myuser
export PROXY_PASSWORD=mypass
npm start

Each browser context gets a unique sticky session, so different users get different IP addresses. Sessions rotate automatically on proxy errors or Google blocks.

Or in Docker:

docker run -p 9377:9377 \
  -e PROXY_HOST=166.88.179.132 \
  -e PROXY_PORT=46040 \
  -e PROXY_USERNAME=myuser \
  -e PROXY_PASSWORD=mypass \
  camofox-browser

When a proxy is configured:

  • All traffic routes through the proxy
  • Camoufox's GeoIP automatically sets locale, timezone, and geolocation to match the proxy's exit IP
  • Browser fingerprint (language, timezone, coordinates) is consistent with the proxy location
  • Without a proxy, defaults to en-US, America/Los_Angeles, San Francisco coordinates

Structured Logging

All log output is JSON (one object per line) for easy parsing by log aggregators:

{"ts":"2026-02-11T23:45:01.234Z","level":"info","msg":"req","reqId":"a1b2c3d4","method":"POST","path":"/tabs","userId":"agent1"}
{"ts":"2026-02-11T23:45:01.567Z","level":"info","msg":"res","reqId":"a1b2c3d4","status":200,"ms":333}

Health check requests (/health) are excluded from request logging to reduce noise.

Basic Browsing

# Create a tab
curl -X POST http://localhost:9377/tabs \
  -H 'Content-Type: application/json' \
  -d '{"userId": "agent1", "sessionKey": "task1", "url": "https://example.com"}'

# Get accessibility snapshot with element refs
curl "http://localhost:9377/tabs/TAB_ID/snapshot?userId=agent1"
# → { "snapshot": "[button e1] Submit  [link e2] Learn more", ... }

# Click by ref
curl -X POST http://localhost:9377/tabs/TAB_ID/click \
  -H 'Content-Type: application/json' \
  -d '{"userId": "agent1", "ref": "e1"}'

# Type into an element
curl -X POST http://localhost:9377/tabs/TAB_ID/type \
  -H 'Content-Type: application/json' \
  -d '{"userId": "agent1", "ref": "e2", "text": "hello", "pressEnter": true}'

# Navigate with a search macro
curl -X POST http://localhost:9377/tabs/TAB_ID/navigate \
  -H 'Content-Type: application/json' \
  -d '{"userId": "agent1", "macro": "@google_search", "query": "best coffee beans"}'

API

Tab Lifecycle

Method Endpoint Description
POST /tabs Create tab with initial URL
GET /tabs?userId=X List open tabs
GET /tabs/:id/stats Tab stats (tool calls, visited URLs)
DELETE /tabs/:id Close tab
DELETE /tabs/group/:groupId Close all tabs in a group
DELETE /sessions/:userId Close all tabs for a user

Page Interaction

Method Endpoint Description
GET /tabs/:id/snapshot Accessibility snapshot with element refs. Query params: includeScreenshot=true (add base64 PNG), offset=N (paginate large snapshots)
POST /tabs/:id/click Click element by ref or CSS selector
POST /tabs/:id/type Type text into element
POST /tabs/:id/press Press a keyboard key
POST /tabs/:id/scroll Scroll page (up/down/left/right)
POST /tabs/:id/navigate Navigate to URL or search macro
POST /tabs/:id/wait Wait for selector or timeout
GET /tabs/:id/links Extract all links on page
GET /tabs/:id/images List <img> elements. Query params: includeData=true (return inline data URLs), maxBytes=N, limit=N
GET /tabs/:id/downloads List captured downloads. Query params: includeData=true (base64 file data), consume=true (clear after read), maxBytes=N
GET /tabs/:id/screenshot Take screenshot
POST /tabs/:id/back Go back
POST /tabs/:id/forward Go forward
POST /tabs/:id/refresh Refresh page

YouTube Transcript

Method Endpoint Description
POST /youtube/transcript Extract captions from a YouTube video
curl -X POST http://localhost:9377/youtube/transcript \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "languages": ["en"]}'
# → { "status": "ok", "transcript": "[00:18] ♪ We're no strangers to love ♪\n...", "video_title": "...", "total_words": 548 }

Uses yt-dlp when available (fast, no browser needed). Falls back to a browser-based intercept method if yt-dlp is not installed — this is slower and less reliable due to YouTube ad pre-rolls.

Server

Method Endpoint Description
GET /health Health check
POST /start Start browser engine
POST /stop Stop browser engine

Sessions

Method Endpoint Description
POST /sessions/:userId/cookies Add cookies to a user session (Playwright cookie objects)
GET /sessions/:userId/storage_state Export cookies + localStorage (VNC plugin)

Search Macros

@google_search · @youtube_search · @amazon_search · @reddit_search · @reddit_subreddit · @wikipedia_search · @twitter_search · @yelp_search · @spotify_search · @netflix_search · @linkedin_search · @instagram_search · @tiktok_search · @twitch_search

Reddit macros return JSON directly (no HTML parsing needed):

  • @reddit_search - search all of Reddit, returns JSON with 25 results
  • @reddit_subreddit - browse a subreddit (e.g., query "programming"/r/programming.json)

Environment Variables

Variable Description Default
CAMOFOX_PORT Server port 9377
PORT Server port (fallback, for platforms like Fly.io) 9377
CAMOFOX_API_KEY Enable cookie import endpoint (disabled if unset) -
CAMOFOX_ADMIN_KEY Required for POST /stop -
CAMOFOX_COOKIES_DIR Directory for cookie files ~/.camofox/cookies
CAMOFOX_PROFILE_DIR Directory for persisted session profiles ~/.camofox/profiles
CAMOFOX_TRACES_DIR Directory for session trace zips ~/.camofox/traces
CAMOFOX_TRACES_MAX_BYTES Max size per trace, removed on next startup if exceeded 52428800 (50MB)
CAMOFOX_TRACES_TTL_HOURS Traces older than this are swept on startup 24
MAX_SESSIONS Max concurrent browser sessions 50
MAX_TABS_PER_SESSION Max tabs per session 10
SESSION_TIMEOUT_MS Session inactivity timeout 1800000 (30min)
BROWSER_IDLE_TIMEOUT_MS Kill browser when idle (0 = never) 300000 (5min)
HANDLER_TIMEOUT_MS Max time for any handler 30000 (30s)
MAX_CONCURRENT_PER_USER Concurrent request cap per user 3
MAX_OLD_SPACE_SIZE Node.js V8 heap limit (MB) 128
PROXY_STRATEGY Proxy mode: backconnect (rotating sticky sessions) or blank (single endpoint) -
PROXY_PROVIDER Provider name for session format (e.g. decodo) decodo
PROXY_HOST Proxy hostname or IP (simple mode) -
PROXY_PORT Proxy port (simple mode) -
PROXY_USERNAME Proxy auth username -
PROXY_PASSWORD Proxy auth password -
PROXY_BACKCONNECT_HOST Backconnect gateway hostname -
PROXY_BACKCONNECT_PORT Backconnect gateway port 7000
PROXY_COUNTRY Target country for proxy geo-targeting -
PROXY_STATE Target state/region for proxy geo-targeting -
TAB_INACTIVITY_MS Close tabs idle longer than this 300000 (5min)
ENABLE_VNC Enable VNC plugin for interactive browser access (1) -
VNC_PASSWORD Password for VNC access (recommended in production) -
NOVNC_PORT noVNC web UI port 6080

Architecture

Browser Instance (Camoufox)
└── User Session (BrowserContext) - isolated cookies/storage
    ├── Tab Group (sessionKey: "conv1")
    │   ├── Tab (google.com)
    │   └── Tab (github.com)
    └── Tab Group (sessionKey: "conv2")
        └── Tab (amazon.com)

Sessions auto-expire after 30 minutes of inactivity. The browser itself shuts down after 5 minutes with no active sessions, and relaunches on the next request.

When a session's tab limit is reached, the oldest/least-used tab is automatically recycled instead of returning an error — so long-running agent sessions don't hit dead ends.

Testing

npm test              # all tests
npm run test:e2e      # e2e tests only
npm run test:live     # live site tests (Google, macros)
npm run test:debug    # with server output

npm

npm install @askjo/camofox-browser

Credits

Crypto Scam Warning

Sketchy people are doing sketchy things with crypto tokens named "Camofox" now that this project is getting attention. Camofox is not a crypto project and will never be one. Any token, coin, or NFT using the Camofox name has nothing to do with us.

License

MIT

Reviews (0)

No results found