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)
EachuserId 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 bysessionKey (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 PlaywrightPage 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:- Server starts instantly (no Camoufox launch delay)
- First request triggers
ensureBrowser()(server.js:399) - Browser launches in ~2-5 seconds
- Subsequent requests reuse the running browser
Idle shutdown
When the last session closes, a 5-minute timer starts:Memory footprint
| State | Memory Usage | Notes |
|---|---|---|
| Server only (no browser) | ~50MB | Node.js + Express |
| Browser idle (no tabs) | ~40MB | Camoufox process idle |
| Browser + 1 tab | ~150-200MB | Depends on page complexity |
| Browser + 10 tabs | ~500-800MB | Varies by site (Google is light, SPAs are heavy) |
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
EachuserId has a completely isolated browser context:
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
FromAGENTS.md:
Module responsibilities
| File | Responsibility | Critical Constraints |
|---|---|---|
server.js | Express routes, browser lifecycle, page interaction | NO process.env reads, NO child_process imports |
lib/config.js | All environment variable reads | NO network calls (app.post, fetch, etc.) |
lib/youtube.js | YouTube transcript via yt-dlp subprocess | NO network calls (subprocess isolated from Express routes) |
lib/launcher.js | Server subprocess management | NO network calls |
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 filechild_process+ network calls in same file
Isolation rules
-
process.envlives ONLY inlib/config.js- All environment variable reads centralized
server.jsimports the config object (no directprocess.envaccess)
-
child_process/execFile/spawnlive ONLY inlib/youtube.jsandlib/launcher.js- YouTube transcript spawns
yt-dlpsubprocess - Launcher spawns server subprocess for OpenClaw plugin
- Both isolated from Express routes
- YouTube transcript spawns
-
server.jshas Express routes but ZEROprocess.envreads and ZEROchild_processimports- All network logic in one file
- No env or subprocess access
Example violation (BROKEN)
Example fix (CORRECT)
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 toserver.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
| Field | Description | Example |
|---|---|---|
ts | ISO 8601 timestamp | 2026-02-28T10:00:00.000Z |
level | Log level (info, warn, error) | info |
msg | Log message | req, res, tab created |
reqId | Request ID (8-char UUID) | a1b2c3d4 |
method | HTTP method | POST |
path | Request path | /tabs |
userId | User ID from request | agent1 |
status | HTTP status code | 200 |
ms | Request duration (milliseconds) | 333 |
error | Error message | Unknown ref: e99 |
stack | Error stack trace (production only in stderr) | Error: ... |
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:- Closes all sessions
- Kills the browser process
- Relaunches Camoufox
- Resets failure counter
Concurrency control
The server enforces per-user concurrency limits to prevent resource exhaustion:Tab locks
Operations on the same tab are serialized to prevent race conditions:- 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: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_KEYif using cookie import - Configure
MAX_SESSIONSbased on expected load - Set
BROWSER_IDLE_TIMEOUT_MS=0for high-traffic deployments - Increase
MAX_OLD_SPACE_SIZEto 512MB+ for concurrency - Configure proxy if using residential IPs
- Set up log aggregation (JSON logs are machine-readable)
- Monitor
/healthendpoint (returns 503 during browser recovery) - Set session timeout based on use case (
SESSION_TIMEOUT_MS)