Releases: jo-inc/camofox-browser
Release list
v1.11.2 — CLI Binary
CLI Binary
The npm package now installs a camofox-browser executable.
Run standalone from npm:
npx @askjo/camofox-browserOr install globally:
npm install -g @askjo/camofox-browser
camofox-browserDefault port is still 9377; configure via the same environment variables as source installs.
v1.11.1
ClawHub Publish Fix
Restores the ClawHub runtime id to camofox-browser so the package can publish as an update to the existing @askjo/camofox-browser listing.
No runtime behavior changes from v1.11.0.
v1.11.0 — Windows Support, OpenClaw Compatibility, Production Reliability
Windows Support
Windows users can now build and run camofox-browser without WSL or make.
This release adds a PowerShell build.ps1 workflow, enforces LF line endings for shell scripts, and fixes Docker builds from Windows checkouts by invoking shell scripts through sh. Plugin loading also works correctly on Windows now: plugins are imported via file:// URLs instead of raw filesystem paths.
OpenClaw Compatibility
OpenClaw 2026.5.x requires packages to declare tool ownership in contracts.tools. camofox-browser now ships that manifest metadata, fixing tool registration failures in newer OpenClaw versions.
Cross-Origin Iframe Interaction
Accessibility snapshots now traverse supported iframes, and refs can resolve back into their owning frames. This makes embedded checkout/payment fields visible and clickable through the normal ref-based flow.
Tracking and analytics iframes are filtered out so snapshots stay focused on actionable page content.
Security Hardening
Several sensitive routes now require configured auth:
POST /tabs/:tabId/evaluateDELETE /sessions/:userIdPOST /pressure/cleanup
Cookie import now uses the shared auth middleware too. If you expose camofox-browser beyond loopback, verify your CAMOFOX_API_KEY / CAMOFOX_ACCESS_KEY setup before upgrading.
Production Reliability
This release pulls in reliability fixes from jo production usage:
- Idle shutdown no longer leaks orphaned Camoufox processes
- Browser close now force-cleans escaped child processes
- Health checks stay green after idle browser shutdown
- Stale tab errors are clearer and more recoverable
- Active tab operations refresh session access time so live sessions are not expired
- Popups from
target=_blankandwindow.openare tracked as managed tabs - Tab operations are better serialized with per-tab locks
- Navigation timeout cleanup avoids poisoning proxy-backed sessions
- YouTube transcript session cleanup handles races more safely
- Crash/stall reporter no longer treats
429relay responses as successful delivery
Fixes & Improvements
- Prometheus metrics are lazy-loaded and disabled by default unless explicitly enabled
- Snapshot size metrics added
- Navigate requests are aborted when their tab is deleted
- Retry-path
constreassignment crashes fixed - VNC plugin config is explicit and disabled by default
- Optional Sentry integration no longer prevents server startup when Sentry is not installed
Thank You
Thanks to:
- @kgarg2468 — OpenClaw contracts and Windows/Electron feedback
- @SirBrenton — reporter 429 handling
- @sebastiondev — auth hardening
- @Chen-538 — Windows plugin loading fix
- @dawsman — VNC config cleanup
- @pasmud — Windows build support
v1.9.1
Memory Leak Reporter Improvements
Reduced noise from auto-reported memory leak issues by ~90%.
Changes
- Raised reporter threshold from 200MB → 400MB — the self-healing restart mechanism already handles growth at 200MB when idle, so we only report when self-healing failed
- Added session gate — skip reports when sessions=0 and browser is already dead (restart handled it). Extreme growth (>600MB) still reports as a safety net.
- Capture browser RSS during growth —
lastSeenBrowserRssMbis now captured while the browser is alive (previously always null by report time). Enables distinguishing Node/Playwright leaks from Firefox shared-memory bleed. - Auto-close bot now handles
memory-leakissues with 0 contexts + 0 tabs (same pattern as stuck-issue handling) - Fixed misleading code comment — native memory metric measures Node/Playwright state (CDP buffers, glibc arenas), not Firefox's jemalloc
Context
Audit of 78 auto-reported memory-leak issues found all were self-healing conditions generating noise. The memory pressure restart (kills browser at 200MB growth when idle) was already handling these, but the reporter fired at the same threshold, creating duplicate signals for resolved conditions.
v1.9.0 — Viewport Testing, Tab Leak Fix, Session Robustness
Responsive Viewport Testing
POST /tabs/:tabId/viewport — set any viewport size on a live tab for responsive testing. Pass { width, height } and the page re-renders at that size. Useful for testing mobile breakpoints, tablet layouts, or unusual screen dimensions without creating a new session.
Tab Leak Fix
Two fixes for pages that survive normal cleanup:
safePageCloseforce-kill: Ifpage.close()hangs (e.g., stuck unload handler), the page is force-closed after timeout instead of leaked. Previously a single stuck page would starve Firefox of DOM threads.- Orphan page reaper: Background interval detects Playwright pages that escaped
tabGroupstracking and force-closes them.
Session Lifecycle Robustness
- Navigation timeout → session destroy: A poisoned proxy (Cloudflare holding a connection for 30s) used to kill all subsequent navigations in that context. Now click/navigate/open_url timeouts destroy the session so the next request gets a fresh proxy.
- Drain locks before close: Queued tab operations get clean
410 Tab destroyedinstead of cascading500 Target page closederrors. - Dead-context → 503: Cascade errors return
session_expiredwith retry semantics instead of generic 500. - Transparent proxy retry: Navigation failures from proxy blocks or timeouts automatically retry once with a fresh session.
- goBack timeout 10s → 20s: Back navigation uses browser cache; 10s was too short for complex SPA re-renders.
Install Hardening
- Ship compiled
plugin.jsso install works even when TypeScript compilation is skipped — by @Yeraze (#2140) - Cross-platform postinstall script with cache validation — by @gustavosmendes (#2149)
- Graceful handling of
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1(warns + skips warm-up instead of crash loop) — by @gustavosmendes (#2149)
Fixes & Improvements
- Native memory pressure: restart browser when idle if heap exceeds threshold
- Memory leak false-positive reduction (3 safeguards for watchdog stall detection)
- Crash reporter renamed to telemetry, credentials moved to Cloudflare Worker relay
- OpenClaw plugin metadata + ClawHub publish workflow
Thank You
- @cunninghambe — co-authored viewport endpoint and tab leak fix (#2470, #2471)
- @gustavosmendes — install hardening and
PLAYWRIGHT_SKIP_BROWSER_DOWNLOADsupport (#2149) - @Yeraze — compiled plugin.js fix (#2140)
- @Xy2002 — Railway deployment configuration (#651)
v1.8.0 — Global Access Key, Memory Leak Fix
Global Access Key
CAMOFOX_ACCESS_KEY env var — by @trader-payne (#586)
If you expose camofox beyond loopback — on a VPS, in a Docker network, behind a reverse proxy — you've needed to rely on network-level controls or CAMOFOX_API_KEY (which only gates cookie import). There was no single switch to require auth on every route.
Set CAMOFOX_ACCESS_KEY and every request must carry Authorization: Bearer <key>. Three routes are conditionally exempt:
GET /health— always open (Docker/Fly healthchecks need it)POST /sessions/:userId/cookies— exempt only whenCAMOFOX_API_KEYis also set (has its own gate)POST /stop— exempt only whenCAMOFOX_ADMIN_KEYis also set (has its own gate)
If the dedicated key for an exempted route is not configured, the access key still gates it — defense-in-depth, no accidentally unprotected endpoints.
The access key also works as a superkey on requireAuth() routes, so you don't need two tokens in a single request. 401 responses include WWW-Authenticate: Bearer realm="camofox" per RFC 7235. Env var values are whitespace-trimmed to prevent copy-paste mistakes.
Fully opt-in. If you don't set the env var, nothing changes.
Native Memory Leak Fix
browser.close() had a race condition that could lose the browser PID, leaving orphaned Firefox child processes alive after the context was torn down. On long-running servers this compounded — ~930MB leaked per orphaned browser tree.
Three fixes: closeBrowserFully() serializes concurrent close calls with a shared promise so the PID is never lost, _forceKillProcessTree() walks /proc to find and SIGKILL orphaned children that escaped the process group, and cleanupStaleFirefoxProfiles() sweeps leftover temp directories from enable_cache: true sessions on startup.
Fixes & Improvements
- Crash reporter noise reduction: stable dedup signatures, active-tab gate, per-type rate limits, sleep/suspend false-positive suppression
- npm publishing via OIDC trusted publishing — provenance attestations on every release, no
NPM_TOKENsecret - Docker images published to GHCR on release:
docker pull ghcr.io/jo-inc/camofox-browser:1.8.0
Thank You
Thanks to @trader-payne for the access key feature.
v1.7.2
Structured Extract
POST /tabs/:tabId/extract — by @mvanhorn (#70)
Every agent that uses camofox-browser eventually writes the same code: take an accessibility snapshot, find the refs you care about, parse the name field out of each line by hand, coerce strings to numbers or booleans, trim whitespace. The extract endpoint collapses that into one request with a JSON Schema.
Pass a schema with x-ref hints pointing at snapshot refs, get back a typed object with coercion handled. No LLM, no inference, no external calls — just "look up these refs and coerce them to these types." Think the deterministic half of Stagehand's extract().
POST /tabs/:tabId/extract
{
"schema": {
"type": "object",
"properties": {
"title": { "type": "string", "x-ref": "e1" },
"price": { "type": "number", "x-ref": "e3" },
"inStock": { "type": "boolean", "x-ref": "e4" }
}
}
}
→ { "ok": true, "data": { "title": "Widget", "price": 29.99, "inStock": true } }Session Tracing
POST /tabs with trace: true — by @mvanhorn (#68)
Every major competitor ships session capture — Steel Browser rebuilt its replay stack around MP4, Browserbase leads its observability docs with video replay, AWS Bedrock AgentCore stores recordings in S3. Camofox shipped structured logs and single-shot screenshots, which wasn't enough to reconstruct what the agent actually saw during a multi-step run.
Playwright's recordVideo doesn't work on Firefox (it uses a Chrome-only CDP method). So this uses context.tracing instead, which actually gives you more than video: screenshots + DOM snapshots + network + JS stacks + console output, all in one zip you open with npx playwright show-trace.
Pass trace: true when creating a tab. When the session closes, the trace is flushed to disk. List and download traces via /sessions/:userId/traces. TTL + size-cap sweep runs on startup. Trace directories are SHA-256-hashed per user. Default off, opt-in per session.
OpenAPI Docs
GET /docs — (#78, inspired by @mvanhorn's #69)
Auto-generated spec from @openapi JSDoc annotations on all 31 routes via swagger-jsdoc. Interactive docs at /docs using swagger-stripey. Drift-proof: tests fail if you add a route without @openapi or remove a route but leave the annotation. Legacy endpoints (/act, /navigate, /snapshot, /start, /stop) marked deprecated.
Crash & Hang Telemetry (on by default)
Browser automation fails in ways that are hard to predict — Cloudflare challenges, site redesigns breaking selectors, redirect loops, renderer crashes. The scope is wide and the failure modes are diverse. Without telemetry, the only signal is "it didn't work."
This is on by default, and that's deliberate. The failure surface of headless browser automation is enormous — every site is different, anti-bot measures change weekly, and edge cases compound across browsers, OSes, and network conditions. We can't make camofox rock-solid without knowing what actually breaks in the wild. We invested heavily in anonymization (see below) so we could make this the default responsibly. If you disagree with the tradeoff, one env var turns it off.
The crash reporter gives us structured data on which sites fail, how they fail, and how often, so we can prioritize fixes for the patterns that actually affect users. It files GitHub Issues automatically — on this repo — when:
- Uncaught exceptions crash the process
- Event loop stalls exceed 5 seconds (watchdog detection, with sleep/suspend suppression so laptop lids don't trigger false reports)
- Frustration patterns — 3+ consecutive failures (timeout, dead context, navigation abort) on the same tab
Each report is structured into sections designed for fast triage:
Environment — version, Node version, platform, uptime.
Resources — Node RSS + heap, browser process RSS (the one everyone misses — the browser OOMs, not Node), open file descriptors, active libuv handles, browser context count, active tab count. This is the difference between "it crashed" and "it crashed because 47 tabs consumed 4GB."
Hang Details — operation name, duration, time spent waiting for the tab lock vs. time in the actual operation, document.readyState at timeout (instantly tells you which layer is stuck: loading = network, interactive = heavy JS, complete = Playwright waitUntil condition not met, unresponsive = renderer crashed), in-flight request count.
Anti-Bot Detection — automatically classifies whether a hang was caused by bot detection rather than a camofox bug. Identifies the provider (Cloudflare, DataDome, PerimeterX, Distil, Sucuri, Akamai) from response headers, reports the HTTP status, redirect chain length + status codes, and response body size. Bot-detected hangs get a bot-detection label so they're filtered out of real bug triage.
Proxy — whether a proxy was configured, type (HTTP/SOCKS5), whether auth was configured, and classified error codes (ERR_PROXY_CONNECTION_FAILED, ERR_TUNNEL_CONNECTION_FAILED, ERR_PROXY_AUTH_REQUESTED, ERR_PROXY_TLS) — no IPs, hostnames, ports, or credentials.
Stall Details — event-loop stall duration, last Express route handler, active handles/requests, heap delta during the stall (positive = memory pressure, negative = GC was the cause).
What it never captures:
- Page content, DOM, screenshots, cookies, headers, request/response bodies
- User-entered data, form fields, credentials
- Proxy IPs, hostnames, ports, or credentials
- Environment variables or system configuration
- Session traces — even if tracing is enabled, traces stay on disk and are never sent to the reporter
How URLs are anonymized:
- Public infrastructure (Cloudflare, Google, GitHub, npm) preserved verbatim so we can identify which CDNs cause problems
- All other domains → stable HMAC hash (
site-a1b2c3d4) — same hash across reports for correlation, not reversible to the original - Paths → depth only (
•/•/•), query params → count only (?[3]). No keys, values, or path content ever included - Tokens, secrets, API keys →
<token>. IPs, emails → redacted
How it works:
- Reports filed as GitHub Issues via dedicated GitHub App (issues-only permissions, no PAT needed)
- Rate limited to 10/hour, deduplicated by stack signature (same crash = comment on existing issue, not a new one)
- Fully overridable: Set your own GitHub App credentials in the
crashReportersection ofcamofox.config.jsonto route reports to your org's private repo instead - Disable with
CAMOFOX_CRASH_REPORT_ENABLED=false
Fixes & Improvements
session:destroyingevent: New lifecycle event fires beforecontext.close(), while the Playwright context is still alive — use it for persistence checkpoints. The existingsession:destroyedstill fires after cleanup for backward compatibility. By @nobita2041 (#75)- CI overhaul: GitHub Actions with full browser-backed test suite, Node 24 only, parallel unit + e2e jobs. CI initially set up by @hnshah
Discussions
We've turned on Discussions on the repo.
Thank You
Thanks to @mvanhorn for structured extract and session tracing, @nobita2041 for the session lifecycle fix, and @hnshah for getting CI off the ground.
v1.6.0 — Plugin System, Persistence, VNC
Camofox Browser v1.6.0
What's New
🔌 Plugin System
Camofox is now extensible. Features can be built as self-contained plugins with their own dependencies, config, routes, and tests — loaded automatically from plugins/.
The plugin system includes an event bus with async mutating hooks (emitAsync()) so plugins can intercept and transform 29 browser events across 7 categories (session creation, navigation, downloads, and more), shared auth so plugins inherit the server's API key, and per-plugin configuration in camofox.config.json. A plugin manager handles installation:
node scripts/plugin.js install ./my-plugin
node scripts/plugin.js remove my-plugin
node scripts/plugin.js listPlugins auto-install their own npm dependencies on startup. Want to add custom auth flows, logging, content filtering, or site-specific logic? Write a plugin — no need to fork the server. Three built-in plugins ship with this release to show the pattern.
💾 Persistence Plugin (enabled by default)
Browser sessions now survive restarts. Cookies and localStorage are saved per-user and restored automatically on the next session. No more re-authenticating after every reboot. Profiles are stored at ~/.camofox/profiles/ by default — configure it in camofox.config.json:
{
"plugins": {
"persistence": {
"profileDir": "/your/custom/path"
}
}
}(Thanks @leoneparise #50 and @eddieoz #55 for the session management groundwork.)
🖥️ VNC Plugin (opt-in) (Thanks @leoneparise! #65)
Some sites just won't let you log in programmatically — CAPTCHAs, MFA flows, or bot detection that blocks headless browsers. The VNC plugin lets you see and control the browser visually in your web browser. Log in by hand like a normal user, and the persistence plugin automatically saves that authenticated session for headless use going forward.
Enable it in camofox.config.json:
{
"plugins": {
"vnc": { "enabled": true }
}
}Then open http://localhost:6080 to interact with the browser.
🎬 YouTube Plugin (enabled by default)
YouTube transcript extraction moved from the server core into plugins/youtube/. Functionally identical — just better organized, and a good reference for how to build your own plugin.
⌨️ Unified /type Endpoint (Thanks @LolipopJ! #66)
The /type endpoint now supports a keyboard mode for key presses, modifier keys, and shortcuts alongside the existing text input mode.
🐳 Multi-Arch Dockerfile (Thanks @company8! #51)
Docker builds now download Camoufox and yt-dlp at build time with proper architecture detection. ARM and x86 builds just work.
🐛 Fixes
- Orphaned Camoufox temp files cleaned up on startup
CAMOFOX_PORTrestored to 9377 (was broken in a previous refactor)- Async plugin hooks now properly awaited for mutating events
- Plugin dependency installer handles both string and object config formats
📦 Dependencies
- Security bumps via dependabot (#63)
Tests
347 tests passing (+44 new). Auth, plugin loading, persistence, VNC, and keyboard mode all covered.
Upgrading
Drop-in replacement for 1.5.x. No breaking changes, no configuration changes needed.
Thank You
Our biggest community release — 8 PRs from 6 contributors: @leoneparise (#50), @company8 (#51), @eddieoz (#55), @jecruz (#61), @mvanhorn (#62, #65), @LolipopJ (#66).
v1.5.0 — Proxy Providers, Google Bot Detection, Tab Recycling
Camofox Browser v1.5.0
What's New
🔌 Proxy Provider Abstraction
- Proxy configuration is now provider-agnostic. Supports backconnect (sticky session rotation) and simple (single endpoint) strategies via environment variables — no more hardcoded provider assumptions.
- Per-context proxy rotation: new browser contexts get fresh proxy sessions without restarting the entire browser.
- New env vars:
PROXY_STRATEGY,PROXY_PROVIDER,PROXY_BACKCONNECT_HOST,PROXY_BACKCONNECT_PORT,PROXY_COUNTRY,PROXY_STATE.
🛡️ Google Bot Detection Fixes
- WebGL support: Added Mesa OpenGL/EGL libraries + Xvfb virtual display to Docker image. Firefox can now create WebGL contexts — a major bot detection signal that was previously missing.
- Self-healing Google search: blocked tabs are automatically retried with fresh proxy sessions instead of returning errors.
- Degraded mode: browser stays available for non-Google tasks even when all proxy sessions are Google-blocked.
♻️ Tab Recycling
- When the per-session tab limit is reached, the oldest/least-used tab is recycled instead of returning a hard 429 error. Long-running agent sessions no longer hit dead ends.
🚀 Fly.io Horizontal Scaling
- New
lib/fly.jshelpers:fly-replayheader routing for sticky tab sessions across multiple machines, concurrency-based autoscaling support.
🐳 Makefile for Local Builds (Thanks @mihado! #43)
make upauto-detects CPU architecture (x86_64/aarch64), pre-downloads Camoufox + yt-dlp outside the Docker build for faster rebuilds (~30s vs ~3min), and starts the container.make build,make down,make reset,make clean— full lifecycle management.
🐛 Bug Fixes
- YouTube transcript proxy: yt-dlp routed through residential proxy with lazy retry on startup failure.
- YouTube transcript session leak: phantom
__yt_transcript__session cleaned up after use. (Thanks @imtylervo! #39) - Slow page refs: Element references no longer go stale on pages that take a long time to settle. (#36)
- DELETE userId:
/tabs/:idDELETE endpoints now acceptuserIdfrom query string — no longer silently fails when HTTP clients strip request bodies. (Thanks @imtylervo! #39) - Scroll left/right: Horizontal scroll now works correctly instead of silently scrolling vertically. (Thanks @imtylervo! #39)
- Plugin orphan process: Server process is now killed on health check failure during startup instead of leaking. (Thanks @imtylervo! #39)
- Timer leak:
buildRefsinterval is cleared on tab close. (Thanks @imtylervo! #39) - Plugin stringify: Tool parameters are properly JSON-serialized. (#35)
📦 Dependency Bumps
- brace-expansion 1.1.13, qs 6.14.2, picomatch 2.3.2, path-to-regexp 0.1.13
Upgrading
Update your plugin to v1.5.0. If you use a proxy, the old PROXY_HOST/PROXY_PORT env vars still work for simple single-endpoint proxies. For rotating sticky sessions, set PROXY_STRATEGY=backconnect — see README for details.
v1.4.1 — Metrics, Proxy Fix, Stability
Camofox Browser v1.4.1
What's New
📊 Prometheus Metrics
- New
/metricsendpoint exposes Prometheus-compatible metrics: active tabs, request latency (by endpoint), browser restarts, tab lifecycle events, and more. Drop it into your existing monitoring stack.
🐛 Bug Fixes
- Firefox proxy credentials: Fixed base64 auth header decoding for Firefox-style proxy credential format.
- Macro validation: LLM-generated garbage values (
__NO__,none,null) no longer cause 500 errors — they're filtered out and treated as empty. Missingurl/macronow returns 400 instead of 500. - goBack navigation: Pages that cancel navigation (
NS_BINDING_CANCELLED) no longer throw unhandled errors. - Idle browser shutdown: The idle timer now starts after browser pre-warm, so unused browsers don't stay alive indefinitely (~735 MB RSS savings). (Thanks @rplakas! #33)
- Plugin evaluate fix:
camofox_evaluaterequest body is now properly JSON-serialized. (Thanks @kays0x! #35)
Upgrading
Update your plugin to v1.4.1. No configuration changes needed — fully backward compatible.