redf0x1/camofox-browser
Anti-detection browser server for AI agents — REST API wrapping Camoufox engine with OpenClaw plugin support
🐛 Fixed
- Fixed fresh npm installs resolving incompatible Playwright builds that could crash Camoufox on first tab launch with a `Browser.setDefaultViewport` / `isMobile` protocol error.
- `playwright-core` is now pinned to `1.58.1`, matching the Camoufox binary/protocol bundle used by `camoufox-js@0.8.5`.
📦 Upgrade
- Upgrade to `camofox-browser@2.4.6`.
- If your project uses npm overrides, pnpm overrides, Yarn resolutions, or a manually installed `playwright-core`, make sure it does not force Playwright above `1.58.1` on this release line.
📦 Verification
- npm `camofox-browser@2.4.6` is published with the `latest` tag.
- A clean npm install resolves `playwright-core@1.58.1`.
- `npm run verify:package` passed.
- Full `npm test` passed: 64 suites and 495 tests.
📋 What changed
- Added CAMOFOX_AUTH_MODE=auto|required|disabled.
- auto preserves the existing secure default: loopback can run without an API key, while non-loopback binds require CAMOFOX_API_KEY.
- required forces CAMOFOX_API_KEY for every bind, including loopback.
- disabled explicitly disables API-key auth for trusted private agent networks whose clients cannot send bearer tokens, including Hermes/OpenClaw/GoClaw-style deployments.
📦 Guardrails
- CAMOFOX_AUTH_MODE=disabled must run without CAMOFOX_API_KEY to avoid a false sense of protection.
- Non-loopback disabled-auth deployments keep private-network navigation blocked by default.
- Startup fails if CAMOFOX_AUTH_MODE=disabled is combined with CAMOFOX_ALLOW_PRIVATE_NETWORK=true on a non-loopback bind.
- Use disabled mode only on trusted private networks and do not publish the service to untrusted networks.
📦 Verification
- npm test: 63 suites passed, 2 skipped; 494 tests passed.
- npm run build passed.
- npm audit --audit-level=moderate found 0 vulnerabilities.
- npm pack --dry-run produced shasum fc3ba6b90f96694f481c125c870ebb2d57d4b17b.
- Resolves #20.
📋 Changes
- First tab creation now reuses the browser engine's initial untracked about:blank page when safe, so headed and virtual-display sessions no longer open an extra empty window next to the requested page.
- Refreshed Express/body-parser/qs and CI reporter dependency locks so npm audit --audit-level=moderate reports zero vulnerabilities.
- npm run lint
- npm test: 63 passed, 2 skipped suites; 482 passed, 18 skipped tests
- npm run test:e2e: 26 passed suites; 151 passed, 9 skipped tests
- npm run verify:package: ALL CHECKS PASSED
- npm audit --audit-level=moderate: 0 vulnerabilities
- npm publish --dry-run: camofox-browser@2.4.4 payload verified
📋 Changes
- Applies session-level proxyProfile/raw proxy egress to browser context launch.
- Isolates sibling session/profile state with delimiter-safe encoded keys and profile-keyed persistent directories.
- Hardens trace artifact owner tokens and rejects colliding legacy trace tokens.
- Fixes default/profile session lifecycle cleanup, rollback races, max-tabs default-claim cleanup, display-toggle restart preservation, and ambiguous cookie import handling.
- Aligns README release checklist docs with current env vars, CLI command count, and search macros.
- npm test: 478 passed / 18 skipped.
- npm run test:e2e: 150 passed / 9 skipped.
- Targeted session/context/tracing regression bundle: 48 passed.
- + 3 more
📦 Highlights
- Fix Docker/GHCR release builds so transient `camoufox-js fetch` / GeoLite download failures no longer fail the image build
- Keep the full Wave 2 surface from `v2.4.0` intact
- Preserve package/plugin parity at `2.4.1`
📦 Notes
- This is a focused patch release to repair the release lane after `v2.4.0`
- Local verification, package contract checks, and a regression test for the Dockerfile contract all passed before tagging
📦 Highlights
- Add OpenAPI 3.1 docs at `/openapi.json` and interactive Swagger UI at `/api/docs`
- Ship Wave 2 surfaces: fingerprint env controls, idle lifecycle policy, session-level proxy/geo overrides, and structured extraction
- Bump package and OpenClaw plugin metadata to `2.4.0`
📦 Release Notes
- OpenAPI docs intentionally cover a representative subset of the shipped route surface
- Security defaults remain hardened for exposed deployments (`CAMOFOX_HOST`, API key enforcement, private-network navigation restrictions)
- Package and plugin contract verification passed before tagging
📦 Superseded
- `v2.4.1` supersedes this tag for release distribution after fixing the Docker/GHCR release lane.
📦 Highlights
- Trace artifact management endpoints for listing, downloading, and deleting managed Playwright trace ZIPs per user session
- Image-only extraction route with selector, extension, lazy-load, and blob-resolution controls
- Additional hardening for trace ownership, path validation, timeout cleanup, and chunk-stop coordination
📦 Verification
- `npm test`
- `npm run verify:package`
📦 Publishing Notes
- GitHub release and tag are published in this step
- npm package publish is intentionally pending separate npm auth
Fix: Unknown ref error now returns HTTP 400 (was 500) with guidance to call snapshot first.
📋 Changes
- FIX: CSS selector routing — only /^e\d+$/ is a ref, everything else is CSS selector
- FIX: Single ariaSnapshot call eliminates ref/snapshot race condition
- FIX: Unified snapshot routes (core + OpenClaw use same service)
- NEW: Expanded ARIA roles for refs (combobox, listbox, option, dialog, etc.)
- NEW: Configurable MAX_SNAPSHOT_NODES (env CAMOFOX_MAX_SNAPSHOT_NODES, default 2000)
- NEW: Stale-ref detection with clear HTTP 400 error
- FIX: OpenClaw /act error status propagation (400 vs 500)
- FIX: De-duplicated isElementRef across modules
🐛 Fixed
- Resolved text input truncation at ~500 characters caused by humanize typing delay + 30s handler timeout
- `smartFill()` now uses bulk DOM insertion via `page.evaluate()` for text >= 400 characters
- Dynamic typing timeout replaces fixed 30s limit: 10,000ms base + 80ms per character (max 120,000ms)
📋 Changed
- Short text (<400 chars) continues to use humanized per-character typing for anti-detection
- ContentEditable elements use `document.execCommand('insertText')` for rich text compatibility
✨ Added
- `POST /tabs/:tabId/evaluate-extended` — Execute JavaScript with extended timeout up to 300 seconds
- Configurable timeout (100ms to 300s, default 30s)
- Conditional API key authentication (when `CAMOFOX_API_KEY` is set)
- Per-user fixed-window rate limiting (default: 20 req/minute)
- New env vars: `CAMOFOX_EVAL_EXTENDED_RATE_LIMIT_MAX`, `CAMOFOX_EVAL_EXTENDED_RATE_LIMIT_WINDOW_MS`
- In-memory rate limiter middleware
- Unit + E2E tests for evaluate-extended
📋 Changed
- Refactored `evaluateTab()` to share internal logic — no behavior changes to existing `/evaluate`
🐛 Fixed
- Restored missing `POST /sessions/:userId/toggle-display` route (was causing 404 errors from MCP toggle_display tool)
📦 Install / Update
- ```bash
- npm install camofox-browser@latest
- docker pull ghcr.io/redf0x1/camofox-browser:latest
- ```
🐛 Fixed
- noVNC display scaling — Removed x11vnc -ncache flag that caused 10x framebuffer height
- Custom Xvfb resolution — Replaced camoufox VirtualDisplay (1x1px) with proper 1920x1080 Xvfb
- CAMOFOX_VNC_RESOLUTION env var for custom resolution
✨ Added
- noVNC browser viewer — See the browser GUI through your web browser when in virtual/headed mode
- Auto VNC start/stop — VNC automatically starts when toggling display mode and stops when switching back to headless
- VNC URL in response — toggle-display response includes vncUrl field to open in browser
- CAMOFOX_VNC_TIMEOUT_MS — Configurable VNC session timeout (default: 2 minutes)
- VNC lifecycle management — Automatic cleanup on session delete, context close, and server shutdown
📦 Usage
- 1. Toggle to virtual: POST /sessions/:userId/toggle-display {"headless": "virtual"}
- 2. Open the vncUrl from response in your browser
- 3. Interact with Firefox (solve CAPTCHAs, etc.)
- 4. Toggle back: POST /sessions/:userId/toggle-display {"headless": true}
🐛 Fixed
- Virtual display mode — headless: "virtual" now correctly creates an Xvfb virtual display
- Headed mode in Docker — headless: false auto-falls back to virtual display when no X display is available
- Xvfb in Docker — Added xvfb package to Docker image
- VirtualDisplay lifecycle — Xvfb processes properly cleaned up on context close
✨ Added
- Display mode toggle — New POST /sessions/:userId/toggle-display endpoint to switch browser between headless and headed mode at runtime
- CAMOFOX_HEADLESS environment variable — Configure default display mode: true (headless), false (headed), or virtual (Xvfb)
- Per-user display overrides — Each user session can have its own display mode
🐛 Fixed
- Updated engines.node from >=18 to >=22 to match actual runtime requirement (camoufox-js is ESM-only, requires Node 22+ for CJS interop)
🐛 Fixed
- yt-dlp updated to 2026.02.21 — Previous version had broken nsig extraction
- Narrowed subtitle language selection — `en.*` wildcard was triggering YouTube 429 rate-limiting
- Added `--js-runtimes node` flag — Required by yt-dlp 2026.x EJS system
- Added `--no-abort-on-error` flag — Prevents yt-dlp failing on individual track 429s
- Browser fallback hard timeout — `Promise.race` prevents indefinite Playwright Firefox hangs
- `withUserLimit` operation timeout — Prevents permanently stuck concurrency slots
- Session cleanup concurrency reset — `cleanupSessionsForUserId` now clears the concurrency Map
📦 YouTube Transcript Extraction
- `POST /youtube/transcript` — extract transcripts from YouTube videos
- yt-dlp primary engine with browser fallback
- Multi-language support, automatic subtitle parsing
- Dockerfile includes yt-dlp (pinned v2025.02.19)
📦 Snapshot Truncation & Pagination
- Large pages automatically truncated at 80K chars (configurable via `CAMOFOX_MAX_SNAPSHOT_CHARS`)
- Offset-based pagination for navigating through large snapshots
- Tail preservation ensures end-of-page content is always visible
📦 Browser Health Tracking
- `GET /health` returns 503 during recovery with `consecutiveFailures` and `activeOps`
- Configurable failure threshold and health probe interval
- Build refs timeout (12s) and tab lock timeout (30s)
📦 Click & Navigation Improvements
- `refsAvailable` flag in navigate and click responses
- Auto-refresh stale refs when `refs.size === 0`
- Case-insensitive click timeout detection
- Plugin tools: `camofox_go_back`, `camofox_go_forward`, `camofox_refresh`
✨ New Environment Variables
- | Variable | Default |
- |----------|---------|
- | `CAMOFOX_MAX_SNAPSHOT_CHARS` | `80000` |
- | `CAMOFOX_SNAPSHOT_TAIL_CHARS` | `5000` |
- | `CAMOFOX_BUILDREFS_TIMEOUT_MS` | `12000` |
- | `CAMOFOX_TAB_LOCK_TIMEOUT_MS` | `30000` |
- | `CAMOFOX_HEALTH_PROBE_INTERVAL_MS` | `60000` |
- | `CAMOFOX_FAILURE_THRESHOLD` | `3` |
- + 2 more
📦 Install
- ```
- npm install camofox-browser@1.6.0
- ```
- Full Changelog: https://github.com/redf0x1/camofox-browser/blob/main/CHANGELOG.md
📦 Improved
- Download metadata now includes `contentUrl` for direct file retrieval by AI agents
- Default download TTL increased from 30 minutes to 24 hours
- Download registry persisted to disk (`registry.json`) — survives server restarts
- TTL countdown starts from `completedAt` instead of `createdAt`
- Orphaned download files automatically rebuilt into registry on startup
- Updated AGENTS.md with clear download workflow guide for AI agents
🐛 Fixed
- `finalizeDownload` failure branch now persists registry state
- Click handler download response now includes `contentUrl`
- `buildContentUrl` exported and reused across modules (DRY)
📦 For AI Agents
- Every download response now includes `contentUrl` — just GET it to retrieve the binary file.
🐛 Fixed
- Dockerfile: Added `VOLUME /home/node/.camofox` to make persistence intent explicit
- fly.toml: Added `CAMOFOX_PROFILES_DIR`, `CAMOFOX_COOKIES_DIR`, `CAMOFOX_DOWNLOADS_DIR` env vars for Fly.io volume persistence
✨ Added
- `docker-compose.yml` for easy deployment with volume mount
📝 Docs
- Updated Docker examples in README.md and AGENTS.md with volume mount
📦 Download Lifecycle Management
- Register, list, get, delete downloads with TTL-based cleanup
- Per-user download cap (500 entries) with LRU eviction
- Stream error handling on content delivery
📦 Scoped DOM Resource Extraction
- Extract images, links, media, documents from specific page containers
- CSS selector scoping for targeted extraction
- First-to-market: no existing tool provides scoped extraction as a first-class API
📦 Batch Download Pipeline
- Concurrent downloads with semaphore control
- Data URI support (both base64 and URL-encoded)
- Automatic error recovery for individual download failures
📦 Blob URL Resolution
- Firefox-compatible FileReader.readAsDataURL patt- Fi- Capped at 25 - Firefox-compatible FileReader.re E- Firefoxink Filtering
- `scope` — CSS selector to scope link extraction
- `extension` — Filter by file extension
- `downloadOnly` — Only return download-like links
✨ New REST Endpoints
- `GET /tabs/:tabId/downloads` — List downloads for tab
- `GET /users/:userId/downloads` — List all user downloads
- `GET /downloads/:downloadId` — Get download metadata
- `GET /downloads/:downloadId/content` — Stream download content
- `DELETE /downloads/:downloadId` — Delete download
- `POST /tabs/:tabId/extract-resources` — Extract resources from DOM
- `POST /tabs/:tabId/batch-download` — Batch download resources
- `POST /tabs/:tabId/resolve-blobs` — Resolve blob: URLs
📦 Quality
- 5 quality review rounds (38/40 final score)
- 4 critical + 6 major issues found and fixed during deep review
- 34 new unit tests (173 total)
- npm: `npm install camofox-browser@1.5.0`
✨ Added
- `withTimeout()` utility — configurable handler timeout (default 30s, env: `HANDLER_TIMEOUT_MS`)
- `withUserLimit()` — per-user concurrency limiter (default 3, env: `MAX_CONCURRENT_PER_USER`)
- `safePageClose()` — safe page close with 5s timeout, prevents hung close operations
- Unit tests for all new utility functions
📋 Changed
- ariaSnapshot timeout reduced 10s → 5s for faster failure detection
- ariaSnapshot retry now catches failures gracefully (returns empty refs instead of crashing)
- `getAriaSnapshot()` returns null on failure instead of throwing
- Navigate endpoint returns 400 (not 500) for blocked URL schemes
- All core handlers wrapped with `withTimeout` for request-level timeout protection
- Navigate, snapshot, click handlers wrapped with `withUserLimit` for per-user concurrency control
- OpenClaw /navigate, /act, /snapshot handlers wrapped with `withTimeout` + `withUserLimit`
- All `page.close()` calls replaced with `safePageClose()`
- + 1 more
🗑️ Removed
- Dead code: unused `navigateTab()`, `scrollTab()`, `waitTab()` exports
🔒 Security
- Blocked URL scheme detection returns proper 400 status code
📦 🔒 Persistent Browser Profiles
- Each `userId` now gets a dedicated Firefox profile directory. All browser storage — cookies, localStorage, IndexedDB, Service Workers, and cache — persists automatically across sessions.
- This means sites like Telegram (which uses IndexedDB for auth) and Facebook (cookies) stay logged in even after closing and reopening tabs.
✨ Features
- Persistent browser profiles: Each `userId` gets a real Firefox profile directory that auto-persists ALL browser state
- Per-user Firefox processes via `launchPersistentContext(userDataDir)`
- Context pool manager with LRU eviction and eviction callbacks
- New env var: `CAMOFOX_PROFILES_DIR` (default: `~/.camofox/profiles`)
- Health endpoint now reports pool stats (`poolSize`, `activeUserIds`, `profileDirsTotal`)
📝 Docker
- ```bash
- docker run -d \
- -p 9377:9377 \
- -v ~/.camofox:/home/node/.camofox \
- ghcr.io/redf0x1/camofox-browser:1.3.0
- ```
📦 Install
- ```bash
- npm install camofox-browser@1.3.0
- ```
💥 Breaking / Behavior Changes
- Browser contexts now use persistent Firefox profile directories (instead of ephemeral/in-memory contexts)
- Singleton browser pattern removed in favor of per-user browser processes
📦 Links
- npm: https://www.npmjs.com/package/camofox-browser/v/1.3.0
- Docker (GHCR): https://github.com/redf0x1/camofox-browser/pkgs/container/camofox-browser
- Full Changelog: https://github.com/redf0x1/camofox-browser/blob/main/CHANGELOG.md
🐛 Bug Fixes
- Fix cookie import session mismatch with presets — When tabs use presets (e.g., `vietnam`), cookie import now correctly targets the preset session via optional `tabId` parameter
📋 Full Changelog
- https://github.com/redf0x1/camofox-browser/compare/v1.0.2...v1.0.3
🐛 Bug Fixes
- Add cookie export endpoint — `GET /tabs/:tabId/cookies` for session profile persistence
- API key auth on cookie export (matches cookie import security)
📋 Full Changelog
- https://github.com/redf0x1/camofox-browser/compare/v1.0.1...v1.0.2
🐛 Fixes
- Docker: Fixed `/health` returning 500 — native module (`better-sqlite3`) now builds correctly in container
- CLI: `--help` and `--version` flags now work without starting the server
📦 Install
- ```bash
- npm install -g camofox-browser
- camofox-browser --help
- ```
📝 Docker
- ```bash
- docker build -t camofox-browser .
- docker run -p 9377:9377 camofox-browser
- curl http://localhost:9377/health
- ```
📦 Links
- [npm](https://www.npmjs.com/package/camofox-browser)
- [README](https://github.com/redf0x1/camofox-browser#readme)
- [CamoFox MCP](https://github.com/redf0x1/camofox-mcp)
- [Changelog](https://github.com/redf0x1/camofox-browser/blob/main/CHANGELOG.md)
