开源项目

camofox-browser

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

camofox-browser

面向 AI agent 的反检测浏览器服务器,基于 Camoufox 驱动

License: MIT GitHub stars npm version GitHub last commit

站在 Camoufox 这个巨人的肩膀上——它是一款在 C++ 层面实现指纹伪造的 Firefox 分支。


Jo

由 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)中上报遥测日志:

  1. 创建一个 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})
  2. 部署端点 —— 克隆本仓库并部署 worker:

    cd workers/crash-reporter
    # 编辑 wrangler.toml:将 account_id 设为你的 Cloudflare account ID
    npx wrangler deploy
    

    该 worker 是一个零 npm 依赖的 TypeScript 文件。也可在 Deno、Bun 或任何支持 Web Crypto API 的运行时中运行。

  3. 设置 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
    
  4. 将 camofox-browser 指向你的端点:

    export CAMOFOX_CRASH_REPORT_URL=https://your-worker.your-subdomain.workers.dev/report
    
  5. 验证:

    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]
开源项目jo-inc2026-09-07原文

相关内容