camofox-browser
面向 AI agent 的 stealth 无头浏览器服务,基于 Camoufox(Firefox 分支)在 C++ 层伪造指纹,绕过 Cloudflare 等反爬检测。提供 REST API,可当 Playwright/Puppeteer 直接替换,输出 accessibility snapshot 和稳定 element ref,token 消耗比 HTML 小约 90%。内置会话隔离、cookie 导入、代理/GeoIP、搜索宏等,适合 agent 抓取真实网页。注意:默认开启匿名崩溃遥测,可环境变量关闭。
README
camofox-browser
面向 AI agent 的反检测浏览器服务器,基于 Camoufox 驱动
站在 Camoufox 这个巨人的肩膀上——它是一款在 C++ 层面实现指纹伪造的 Firefox 分支。
由 jo(个人 AI agent) 背后的团队打造。jo 一半运行在你的 Mac 上,一半运行在一台专属于你的云端机器上,完全无需维护。可通过 macOS、Telegram、WhatsApp 和 email 使用。免费试用 beta 版 ->
git clone https://github.com/jo-inc/camofox-browser && cd camofox-browser
npm install && npm start
# -> http://localhost:9377
Why(为什么)
AI agent 需要浏览真实的网络。Playwright 会被拦截,无头 Chrome 会被识别指纹,而 stealth(隐身)插件反而成了最明显的指纹特征。
Camoufox 在 C++ 实现层面 修补 Firefox——navigator.hardwareConcurrency、WebGL renderer、AudioContext、屏幕几何数据、WebRTC——所有这些都在 JavaScript 看到它们之前就已经完成伪造。没有 shim,没有 wrapper,不会泄露痕迹。
这个项目将该浏览器引擎封装成一个面向 agent 的 REST API:用可访问性快照(accessibility snapshot)替代臃肿的 HTML,用稳定的元素引用(element ref)来点击,并为常见网站提供搜索宏。
Features(功能特性)
- C++ Anti-Detection(C++ 反检测) - 绕过 Google、Cloudflare 及大多数机器人检测
- Element Refs(元素引用) - 稳定的
e1、e2、e3标识符,实现可靠交互 - Token-Efficient(Token 高效) - 可访问性快照比原始 HTML 小约 90%
- Runs on Anything(随处可运行) - 浏览器懒启动 + 空闲自动关闭,空闲时内存占用仅约 40MB。设计用于与你的其他技术栈共处一机 —— Raspberry Pi、$5 VPS、共享基础设施都行
- Session Isolation(会话隔离) - 每个用户独立的 cookies/storage
- Cookie Import(Cookie 导入) - 注入 Netscape 格式的 cookie 文件,跳过登录直接访问
- File Upload(文件上传) - 从配置的上传目录附加文件,无需原生 OS 对话框
- Proxy + GeoIP(代理 + GeoIP) - 通过住宅代理转发流量,自动匹配 locale/时区
- Structured Logging(结构化日志) - 带请求 ID 的 JSON 日志行,便于生产环境可观测
- YouTube Transcripts(YouTube 字幕) - 通过 yt-dlp 提取任意 YouTube 视频字幕,无需 API key
- Search Macros(搜索宏) -
@google_search、@youtube_search、@amazon_search、@reddit_subreddit等 10 多个宏 - Snapshot Screenshots(快照截图) - 在可访问性快照之外同时返回 base64 PNG 截图
- Large Page Handling(大页面处理) - 自动截断快照,支持基于 offset 的分页
- Download Capture(下载捕获) - 捕获浏览器下载并可通过 API 获取(可选内联 base64)
- DOM Image Extraction(DOM 图片提取) - 列出
<img>的 src/alt,可选返回内联 data URL - Deploy Anywhere(任意部署) - Docker、Fly.io、Railway
- VNC Interactive Login(VNC 交互式登录) - 通过 noVNC 可视化登录网站,导出 storage state 供 agent 复用
- OpenAPI Docs - 自动生成规范文档,位于
/openapi.json;交互式文档在/docs - Structured Extract(结构化提取) -
POST /tabs/:tabId/extract,通过x-ref将 JSON Schema 属性映射到快照 ref - Session Tracing(会话追踪) - 可选的按会话 Playwright trace 录制(截图 + DOM 快照 + 网络),并提供 API 端点点列、获取和删除 trace zip
- Telemetry(遥测) - 通过 GitHub Issues 自动上报匿名化崩溃/卡死遥测,帮助识别哪些网站导致失败及常见失败模式。私有域名会做 HMAC 哈希,路径/参数会被剥离,token/IP 会脱敏。设置
CAMOFOX_CRASH_REPORT_ENABLED=false可关闭。
Optional Dependencies(可选依赖)
| 依赖 | 用途 | 安装方式 |
|---|---|---|
| yt-dlp | YouTube 字幕提取(快速通道) | pip install yt-dlp 或 brew install yt-dlp |
Docker 镜像中已包含 yt-dlp。本地开发时,如需使用 /youtube/transcript 端点请自行安装。没有 yt-dlp 时,该端点会回退到较慢的基于浏览器的方式。
Quick Start(快速开始)
OpenClaw Plugin(OpenClaw 插件)
openclaw plugins install @askjo/camofox-browser
工具: 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(独立运行)
通过 npm 运行:
npx @askjo/camofox-browser
或从源码运行:
git clone https://github.com/jo-inc/camofox-browser
cd camofox-browser
npm install
npm start # 首次运行会下载 Camoufox(约 300MB)
默认端口为 9377。全部选项见 环境变量。
注意: postinstall 脚本会在获取 Camoufox 二进制文件前,先为自身取消设置
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD。如果没有这个 override,当环境变量中已导出PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1(Playwright 配置为使用系统 Chrome 时很常见)时,二进制下载会被静默跳过,并在运行时导致服务器崩溃。使用外部 Camoufox 可执行文件: 在
npm install前和启动服务器时设置CAMOUFOX_EXECUTABLE=/path/to/camoufox-bin,即可跳过内置下载并使用该可执行文件。兼容别名:CAMOUFOX_EXECUTABLE_PATH和CAMOFOX_EXECUTABLE_PATH。这对 NixOS 路径很实用,如/nix/store/.../camoufox-bin;该可执行文件必须来自包含properties.json、version.json和fontconfig/的 Camoufox 包。离线环境或自定义二进制管理: 如果你已有 Camoufox 包,推荐使用
CAMOUFOX_EXECUTABLE。否则可通过npm install --ignore-scripts禁用自动下载(会跳过所有依赖的生命周期脚本——比较武断),或者更精细地使用npm install --omit=optional并手动对你的镜像源执行npx camoufox-js fetch。注意,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install已不再跳过 Camoufox 下载(postinstall 会在本地清理该环境变量);若想跳过请使用--ignore-scripts或CAMOUFOX_EXECUTABLE。
Docker
附带的 Makefile 会自动检测你的 CPU 架构,并在 Docker 构建之外预下载 Camoufox 和 yt-dlp 二进制文件,因此重新构建非常快(约 30 秒 vs 约 3 分钟)。
# 构建并启动(自动检测架构:M1/M2 为 aarch64,Intel 为 x86_64)
make up
# 停止并移除容器
make down
# 强制干净重建(例如升级 VERSION/RELEASE 后)
make reset
# 只下载二进制文件(不构建)
make fetch
# 显式指定架构或版本
make up ARCH=x86_64
make up VERSION=135.0.1 RELEASE=beta.24
Windows(Windows 系统)
在 Windows 上没有 make。请改用附带的 build.ps1 PowerShell 脚本:
# 构建并启动
.\build.ps1 up
# 停止并移除容器
.\build.ps1 down
# 仅构建镜像
.\build.ps1 build
# 强制干净重建
.\build.ps1 reset
# 仅下载二进制文件(不构建)
.\build.ps1 fetch
# 指定架构
.\build.ps1 up -Arch x86_64
.\build.ps1 up -Arch aarch64
注意: 推荐使用 PowerShell 7+(
pwsh),但powershell.exe(Windows PowerShell 5.1)也可以。脚本需要安装支持 WSL2 后端的 Docker Desktop for Windows。行尾符: 本项目包含一个
.gitattributes文件,它会强制.sh文件使用 Unix(LF)行尾符。如果你已经克隆了仓库并在docker build时遇到sh: not found或set: Illegal option -错误,请运行:Get-ChildItem -Recurse *.sh | ForEach-Object { (Get-Content $_) -join "`n" + "`n" | Set-Content $_ -NoNewline }这样会将 shell 脚本转换为 LF 行尾符。得益于
.gitattributes,未来的克隆会自动处理这一点。
警告:请勿直接运行
docker build。 Dockerfile 使用 bind mount 从dist/拉取预下载的二进制文件。请始终使用make up(或先make fetch再make build)——它会先下载二进制文件。
Fly.io
对于 Fly.io 或其他远程 CI,你需要一个在构建阶段下载二进制文件的 Dockerfile,而不是使用 bind mount。
Railway
项目包含 railway.toml。它使用 Dockerfile.ci(在构建阶段下载二进制文件),并自动将 Railway 的 PORT 环境变量映射为 CAMOFOX_PORT。
# 安装 Railway CLI 后:
railway link
railway up
通过 Railway 控制台或 CLI 设置 secrets:
railway variables set CAMOFOX_API_KEY="your-generated-key"
Usage(使用方法)
Cookie Import(Cookie 导入)
从你的浏览器导入 cookies 到 Camoufox,跳过 LinkedIn、Amazon 等站点的交互式登录。
Setup(配置)
1. 生成一个 secret key:
# macOS / Linux
openssl rand -hex 32
2. 启动 OpenClaw 前设置环境变量:
export CAMOFOX_API_KEY="your-generated-key"
openclaw start
插件(用于认证请求)和服务器(用于校验请求)都使用同一个 key。两者在同一环境中运行——只需设置一次。
为什么要用环境变量? 因为 key 是 secret。
openclaw.json中的插件配置是明文存储的,所以 secrets 不该放在那里。请在 shell profile、systemd unit、Docker env 或 Fly.io secrets 中设置CAMOFOX_API_KEY。
Cookie 导入默认关闭。 如果未设置
CAMOFOX_API_KEY,服务器会对所有 cookie 请求返回 403。
3. 从浏览器导出 cookies:
安装一个可导出 Netscape 格式 cookie 文件的浏览器扩展(例如 Chrome/Firefox 的 "cookies.txt")。导出你想认证的站点对应的 cookies。
4. 放置 cookie 文件:
mkdir -p ~/.camofox/cookies
cp ~/Downloads/linkedin_cookies.txt ~/.camofox/cookies/linkedin.txt
默认目录为 ~/.camofox/cookies/。可通过 CAMOFOX_COOKIES_DIR 覆盖。
5. 让 agent 导入它们:
从 linkedin.txt 导入我的 LinkedIn cookies
agent 会调用 camofox_import_cookies -> 读取文件 -> 使用 Bearer token POST 到服务器 -> cookies 被注入浏览器会话。之后对 linkedin.com 的 camofox_create_tab 调用将自动处于已登录状态。
How it works(工作原理)
~/.camofox/cookies/linkedin.txt (Netscape format, on disk)
|
v
camofox_import_cookies tool (parses file, filters by domain)
|
v POST /sessions/:userId/cookies
| Authorization: Bearer <CAMOFOX_API_KEY>
| Body: { cookies: [Playwright cookie objects] }
v
camofox server (validates, sanitizes, injects)
|
v context.addCookies(...)
|
Camoufox browser session (authenticated browsing)
cookiesPath相对于 cookies 目录解析——阻止目录之外的路径穿越- 每个请求最多 500 个 cookies,文件大小限制 5MB
- Cookie 对象会被净化,只保留 Playwright 字段的允许列表
Session Persistence(会话持久化)
默认情况下,camofox 会将每个用户的 cookies 和 localStorage 持久化到 ~/.camofox/profiles/。浏览器重启后会保留会话——只需通过 cookies 或 VNC 登录一次,后续会话会自动恢复登录状态。
~/.camofox/
|-- cookies/ # Bootstrap cookie files (Netscape format)
\-- profiles/ # Persisted session state (auto-managed)
\-- <hashed-userId>/
\-- storage_state.json
可通过 CAMOFOX_PROFILE_DIR 覆盖目录,或在 persistence 插件配置中设置 "profileDir"。如需禁用持久化,在 camofox.config.json 中设置 "persistence": { "enabled": false }。
默认情况下,storage state 只包含 cookies 和 localStorage。如果还想持久化 IndexedDB,请在 persistence 插件配置中设置 "indexedDB": true。这会捕获所有可序列化的 IndexedDB 记录——不仅是认证数据——并可能使快照明显变大、checkpoint 变慢。
Session Tracing(会话追踪)
录制会话中每个动作的 Playwright trace:页面截图、DOM 快照、网络请求和 console 输出。输出为单个 .zip 文件,可在 Playwright 内置的 Trace Viewer 中打开。
按会话选择启用,在打开第一个 tab 时传递 trace: true:
curl -X POST http://localhost:9377/tabs \
-H 'Content-Type: application/json' \
-d '{"userId":"agent1","sessionKey":"task1","url":"https://example.com","trace":true}'
trace 在会话关闭时写入。先关闭会话以刷新 trace,然后列出、获取和查看:
# 关闭会话以刷新 trace
curl -X DELETE http://localhost:9377/sessions/agent1
# 列出 trace 文件
curl http://localhost:9377/sessions/agent1/traces
# {"traces":[{"filename":"trace-2026-04-18T04-05-00-...zip","sizeBytes":42810,"createdAt":...}]}
# 下载(Content-Type: application/zip)
curl http://localhost:9377/sessions/agent1/traces/trace-2026-04-18T04-05-00-abc.zip > session.zip
# 在 Playwright 的 Trace Viewer 中查看
npx playwright show-trace session.zip
# 删除
curl -X DELETE http://localhost:9377/sessions/agent1/traces/trace-2026-04-18T04-05-00-abc.zip
为什么用 trace 而不是视频:Camoufox 基于 Firefox,而 Playwright 的 recordVideo 仅支持 Chromium。Trace 在 Firefox 上可用,并且提供的信息比视频更多(网络 + DOM + console + 截图)。
Tracing 不能在一个已存在的会话上动态开启。如果需要修改该标记,请先执行 DELETE /sessions/:userId。
存储默认在 ~/.camofox/traces/<hashed-userId>/,服务器启动时会清理:
CAMOFOX_TRACES_DIR- 基础目录(默认:~/.camofox/traces)CAMOFOX_TRACES_MAX_BYTES- 单个 trace 的最大大小,超过后会在下次启动时移除(默认:50MB)CAMOFOX_TRACES_TTL_HOURS- 早于该时间的 traces 会在下次启动时移除(默认: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 / Railway
docker run -p 9377:9377 \
-e CAMOFOX_API_KEY="your-generated-key" \
-v ~/.camofox/cookies:/home/node/.camofox/cookies:ro \
camofox-browser
对于 Fly.io:
fly secrets set CAMOFOX_API_KEY="your-generated-key"
对于 Railway:
railway variables set CAMOFOX_API_KEY="your-generated-key"
Proxy + GeoIP(代理 + GeoIP)
通过代理路由所有浏览器流量,并利用 Camoufox 内置的 GeoIP 根据代理 IP 自动推导 locale、时区和地理位置。
简单代理(单一端点):
export PROXY_HOST=166.88.179.132
export PROXY_PORT=46040
export PROXY_USERNAME=myuser
export PROXY_PASSWORD=mypass
npm start
Backconnect 代理(轮换 sticky session):
适用于 Decodo、Bright Data 或 Oxylabs 等提供单一网关端点并支持基于 session 的 sticky IP 的服务商:
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
每个浏览器 context 都有独立的 sticky session,因此不同用户会获得不同 IP 地址。代理出错或 Google 拦截时,session 会自动轮换。
或者在 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
配置代理后:
- 所有流量都会经过代理
- Camoufox 的 GeoIP 会自动将
locale、timezone和geolocation设为与代理出口 IP 匹配 - 浏览器指纹(语言、时区、坐标)与代理所在位置一致
- 未配置代理时,默认使用
en-US、America/Los_Angeles、旧金山坐标
Telemetry(遥测)
浏览器自动化会以许多难以预测的方式失败——Cloudflare 挑战、网站改版导致选择器失效、重定向循环、对话框风暴、渲染器崩溃。其范围很广,失败模式也五花八门。没有遥测,唯一的信号就是“它没成功”。
遥测为我们提供 哪些网站失败、如何失败、失败频率 的结构化数据,从而让我们能够优先修复真正影响用户的那些模式。满足以下条件时它会自动创建 GitHub Issues:
- 未捕获异常 导致进程崩溃
- 事件循环停顿 超过 5 秒(watchdog 检测)
- 挫败模式 —— 同一 tab 上连续 3 次以上失败(超时、context 失效、导航中断)
每份报告包含失败类型、堆栈跟踪、tab 健康计数器(HTTP 状态直方图、console 错误、请求失败数、重定向深度)以及目标 URL——全部经过匿名化处理。
How it works(工作原理)
遥测数据会发送到轻量级 Cloudflare Worker 端点 https://camofox-telemetry.askjo.workers.dev。该端点将 GitHub App 凭据存储在环境 secrets 中——本包不包含任何 secrets。
lib/reporter.js (client, no secrets)
| anonymize -> POST https://camofox-telemetry.askjo.workers.dev/report
v
Cloudflare Worker (holds GitHub App key)
| validate -> rate-limit -> dedup -> create GitHub Issue
v
GitHub Issue created
端点源代码在本仓库的 workers/crash-reporter/index.ts 中。
Verification(验证方式)
你不用信任我们——可以自行验证线上端点实际运行的代码:
# 1. 询问端点它运行的是什么代码
curl https://camofox-telemetry.askjo.workers.dev/source
# -> { "commit": "abc1234", "sha256": "e3b0c44...", "source": "https://github.com/..." }
# 2. 将 sha256 与本仓库中的源码比对
sha256sum workers/crash-reporter/index.ts
# 3. 检查 commit 与 CI 部署的一致
# https://github.com/jo-inc/camofox-browser/actions/workflows/telemetry-deploy.yml
git log --oneline workers/crash-reporter/index.ts | head -1
如果哈希不匹配,说明端点运行的代码与仓库中不同。部署工作流(.github/workflows/telemetry-deploy.yml)会在部署时注入 commit 和源码哈希——每次部署都可在 GitHub Actions 中审计。
也可以完全跳过验证:设置 CAMOFOX_CRASH_REPORT_ENABLED=false 即可禁用所有遥测,或通过 CAMOFOX_CRASH_REPORT_URL 指向你自己的端点。
Privacy(隐私)
所有上报数据在离开进程之前都会经过极端谨慎的匿名化处理(lib/reporter.js L28-290):
- URL —— 知名的公共域名(Google、Amazon、Reddit、Cloudflare 等)会原样展示,以便我们识别是哪些网站导致问题。私有/未知域名会被替换为稳定的 HMAC 哈希(如
site-a1b2c3d4)——相同的域名在跨报告中有相同的哈希,可用于关联,但无法逆向还原为原始域名。路径段会变成*/*/*(仅保留层级)。query 参数会变成?[3](仅保留数量)。绝不会包含 key、value 或路径内容。 - 文件路径 -> 剥离为仅文件名(
<path>/server.js) - Tokens、secrets、API keys ->
<token> - IP、email、环境变量 -> 脱敏
- Docker/Fly machine IDs ->
<id> - Tab 健康状态 —— 纯计数器(崩溃次数、错误次数、状态码直方图)。不包含页面内容、URL 或用户数据。
重复 issue 会按堆栈签名检测,并追加 +1 评论而不是新建 issue。
# 禁用遥测
export CAMOFOX_CRASH_REPORT_ENABLED=false
# 指向你自己的端点(见下文)
export CAMOFOX_CRASH_REPORT_URL=https://your-endpoint.example.com/report
# 调整速率限制(默认:每小时 10 条)
export CAMOFOX_CRASH_REPORT_RATE_LIMIT=5
Self-hosted telemetry endpoint(自托管遥测端点)
若要在你自己的 GitHub 仓库(而非 jo-inc/camofox-browser)中上报遥测日志:
创建一个 GitHub App —— Settings -> Developer settings -> GitHub Apps -> New
- 权限:Repository -> Issues -> Read & Write
- 取消勾选 Webhook -> Active(不需要)
- 点击 Generate a key —— 会下载一个
.pem文件 - 将 App 安装到目标仓库(Install App -> 选择仓库)
- 记录 App ID(App 的 General 页面上的数字)和 Installation ID(安装后 URL 中的数字:
github.com/settings/installations/{id})
部署端点 —— 克隆本仓库并部署 worker:
cd workers/crash-reporter # 编辑 wrangler.toml:将 account_id 设为你的 Cloudflare account ID npx wrangler deploy该 worker 是一个零 npm 依赖的 TypeScript 文件。也可在 Deno、Bun 或任何支持 Web Crypto API 的运行时中运行。
设置 worker secrets:
cd workers/crash-reporter echo "YOUR_APP_ID" | npx wrangler secret put GH_APP_ID echo "YOUR_INSTALL_ID" | npx wrangler secret put GH_INSTALL_ID # Key 必须是 PKCS#8 DER base64(不能是原始 PEM) openssl pkcs8 -topk8 -inform PEM -outform DER -nocrypt -in your-app.pem | \ base64 | tr -d '\n' | npx wrangler secret put GH_PRIVATE_KEY # 在目标仓库中创建 issues echo "your-org/your-repo" | npx wrangler secret put GH_REPO将 camofox-browser 指向你的端点:
export CAMOFOX_CRASH_REPORT_URL=https://your-worker.your-subdomain.workers.dev/report验证:
curl https://your-worker.your-subdomain.workers.dev/health # -> {"status":"ok"}
Structured Logging(结构化日志)
所有日志输出均为 JSON 格式(每行一个对象),便于日志聚合器解析:
{"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)不会计入请求日志,以减少噪音。
Basic Browsing(基础浏览)
# 创建一个 tab
curl -X POST http://localhost:9377/tabs \
-H 'Content-Type: application/json' \
-d '{"userId": "agent1", "sessionKey": "task1", "url": "https://example.com"}'
# 获取带元素 ref 的可访问性快照
curl "http://localhost:9377/tabs/TAB_ID/snapshot?userId=agent1"
# -> { "snapshot": "[button e1]
