Skip to main content
Camofox Browser is built as a stateful REST API that manages a single Camoufox browser instance with isolated user sessions.

Session hierarchy

The server organizes browser state in a four-level hierarchy:

Browser instance

A single Camoufox browser process runs per server instance. The browser:
  • Launches lazily on first request (no browser on startup)
  • Shuts down after 5 minutes idle with no active sessions (configurable via BROWSER_IDLE_TIMEOUT_MS)
  • Relaunches automatically on next request
  • Uses ~40MB memory when idle (no tabs open)
Lazy launch + idle shutdown allows Camofox to share infrastructure with the rest of your stack. You can run it on a Raspberry Pi, $5 VPS, or shared Railway/Fly.io instance without dedicated resources.

User session (BrowserContext)

Each userId gets an isolated Playwright BrowserContext:
  • Separate cookies (one user can be logged into LinkedIn, another cannot see those cookies)
  • Separate localStorage/sessionStorage
  • Separate cache
  • Sessions timeout after 30 minutes of inactivity (configurable via SESSION_TIMEOUT_MS)
  • Maximum concurrent sessions: MAX_SESSIONS (default 50)
When a proxy is configured, Camoufox’s GeoIP automatically overrides locale, timezoneId, and geolocation to match the proxy’s exit IP.

Tab group (sessionKey)

Within a session, tabs are grouped by sessionKey (or legacy listItemId):
  • Used for conversational context (“task1”, “research_job”)
  • Agents can organize tabs by task or conversation thread
  • Delete entire groups with DELETE /tabs/group/:sessionKey

Tab

Each tab is a Playwright Page with:
  • Unique tabId (UUID)
  • Element refs map (e1, e2, etc.)
  • Visited URLs set
  • Tool call counter (for LRU recycling)
  • Last snapshot cache (for offset pagination)

Lazy browser launch

The browser does not start on server launch. Instead:
  1. Server starts instantly (no Camoufox launch delay)
  2. First request triggers ensureBrowser() (server.js:399)
  3. Browser launches in ~2-5 seconds
  4. Subsequent requests reuse the running browser

Idle shutdown

When the last session closes, a 5-minute timer starts:
If a new request arrives before timeout, the timer is cleared and the browser stays running.
Set BROWSER_IDLE_TIMEOUT_MS=0 to disable idle shutdown. Useful for high-traffic deployments where you want the browser always ready.

Memory footprint

The MAX_OLD_SPACE_SIZE variable controls Node.js V8 heap (default 128MB). Increase to 512MB or 1GB for high-concurrency deployments.

Session isolation model

Each userId has a completely isolated browser context:
Cookie import (POST /sessions/:userId/cookies) only affects that user’s context. Other users cannot access those cookies.

Security implications

  • Multi-tenant safe: One user cannot steal another’s session
  • Cookie import is disabled by default (requires CAMOFOX_API_KEY)
  • Bearer token authentication for cookie import endpoint
  • Path traversal protection for cookie file reads
  • Max 500 cookies per request (prevents DoS)
  • Sanitized cookie fields (removes unknown Playwright fields)

File structure

From AGENTS.md:

Module responsibilities

OpenClaw scanner isolation

OpenClaw’s skill-scanner flags plugins that show potential credential exfiltration patterns:
  • process.env + network calls (app.post, fetch, http.request) in same file
  • child_process + network calls in same file
These patterns suggest a plugin reading secrets from environment and sending them over the network.

Isolation rules

CRITICAL: No single .js file may contain both halves of a scanner rule pair.
  1. process.env lives ONLY in lib/config.js
    • All environment variable reads centralized
    • server.js imports the config object (no direct process.env access)
  2. child_process / execFile / spawn live ONLY in lib/youtube.js and lib/launcher.js
    • YouTube transcript spawns yt-dlp subprocess
    • Launcher spawns server subprocess for OpenClaw plugin
    • Both isolated from Express routes
  3. server.js has Express routes but ZERO process.env reads and ZERO child_process imports
    • All network logic in one file
    • No env or subprocess access

Example violation (BROKEN)

Example fix (CORRECT)

Now server.js has no child_process import, and lib/youtube.js has no network calls.

Why this matters

This was broken in v1.3.0 when YouTube transcript was added directly to server.js, causing OpenClaw’s scanner to flag the plugin. Fixed in v1.3.1 by moving subprocess logic to lib/youtube.js.
When adding new features that need env vars or subprocesses, put that code in a lib/ module and import the result into server.js.

Structured logging

All logs are JSON (one object per line) for easy parsing by log aggregators:

Log fields

Filtering logs

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

Browser health tracking

The server monitors browser health and automatically restarts after consecutive failures:
After 3 consecutive navigation failures, the server:
  1. Closes all sessions
  2. Kills the browser process
  3. Relaunches Camoufox
  4. Resets failure counter
This makes the server self-healing for transient browser crashes.

Concurrency control

The server enforces per-user concurrency limits to prevent resource exhaustion:
Default: 3 concurrent requests per user. Requests beyond this limit queue for up to 30 seconds.

Tab locks

Operations on the same tab are serialized to prevent race conditions:
This prevents issues like:
  • Clicking while navigation is in progress
  • Building refs while page is still loading
  • Concurrent clicks on same element

Host OS detection

Camoufox generates fingerprints matching the host OS:
This ensures navigator.platform, navigator.userAgent, and WebGL renderer strings match the actual OS, reducing detection risk.

Production checklist

Before deploying:
  • Set NODE_ENV=production (hides detailed errors)
  • Generate CAMOFOX_API_KEY if using cookie import
  • Configure MAX_SESSIONS based on expected load
  • Set BROWSER_IDLE_TIMEOUT_MS=0 for high-traffic deployments
  • Increase MAX_OLD_SPACE_SIZE to 512MB+ for concurrency
  • Configure proxy if using residential IPs
  • Set up log aggregation (JSON logs are machine-readable)
  • Monitor /health endpoint (returns 503 during browser recovery)
  • Set session timeout based on use case (SESSION_TIMEOUT_MS)