A CDP to Firefox BiDi protocol proxy.
Note
This is a hostile fork of VulpineOS/foxbridge maintained for use in testing Anubis via Gubal.
foxbridge translates Chrome DevTools Protocol (CDP) into Firefox's Juggler and WebDriver BiDi protocols. Any tool built for Chrome — Puppeteer, browser-use, Playwright CDP mode — can control Firefox-based browsers without modification.
CDP Client (Puppeteer, etc.)
│
▼ WebSocket (ws://localhost:9222)
┌────────────────────────────────────┐
│ foxbridge │
│ CDP Server + HTTP Discovery │
│ Bridge Layer (method translation) │
│ ├── Juggler Backend (pipe FD 3/4) │
│ └── BiDi Backend (WebSocket) │
└────────────────────────────────────┘
│
▼
Firefox
- Most AI agent and automation tools only speak CDP, but Firefox exposes Juggler and WebDriver BiDi instead
- No existing tool bridges CDP to Firefox's automation protocols
- Dual backend: Juggler (Playwright's protocol, production-ready) and WebDriver BiDi (W3C standard, future-proof)
Install the foxbridge binary (needs Go 1.26+), then point it at a Firefox
binary:
go install github.com/TecharoHQ/foxbridge/cmd/foxbridge@latest
foxbridge --binary /usr/bin/firefox --headless --backend bidiThat command:
--binary /usr/bin/firefox— launches this Firefox binary (drop the flag to let foxbridge auto-detect one on yourPATH).--headless— runs Firefox without a visible window; omit it to watch the browser drive.--backend bidi— translates CDP to Firefox's WebDriver BiDi protocol. Drop the flag to use the default Juggler backend instead.
The bidi backend works with stock Firefox. Its full command surface
(navigation, script evaluation, screenshots, cookies, request interception, and
input) is smoke-tested against every archived release from Firefox 124 through
152 — see Testing.
124 is the floor. Firefox's WebDriver BiDi storage module (used for
cookies) landed in 124, so that is the oldest release where the whole surface
works. Firefox 122–123 work for everything except cookies. On Firefox 120 and
older, BiDi is too incomplete — Firefox closes the connection when foxbridge
subscribes to events it doesn't implement — so those are unsupported. foxbridge
subscribes to events one at a time and tolerates unsupported ones, so it still
starts and serves discovery on older browsers, but expect degraded behavior.
The juggler backend needs a Firefox build that ships the Juggler protocol
(e.g. Playwright's Firefox or Camoufox); stock Firefox does not have it, so use
--backend bidi there.
foxbridge launches Firefox, then serves a CDP endpoint on ws://localhost:9222.
Point any CDP tool at it:
// Puppeteer
puppeteer.connect({ browserWSEndpoint: 'ws://localhost:9222' });Working from a checkout of this repo? Swap foxbridge for go run ./cmd/foxbridge to run straight from source without installing anything:
go run ./cmd/foxbridge --binary /usr/bin/firefox --headless --backend bidi# Juggler backend (default) on the default port
foxbridge --binary /usr/bin/firefox
# BiDi backend on a custom port
foxbridge --binary /usr/bin/firefox --backend bidi --port 9222
# Connect to an already-running BiDi endpoint (don't launch Firefox)
foxbridge --backend bidi --bidi-url ws://localhost:9223/session
# Serve CDP over a Unix domain socket instead of a TCP port
foxbridge --binary /usr/bin/firefox --socket /tmp/foxbridge.sock
# Deterministic runtime shims for low-flake tests
foxbridge --binary /usr/bin/firefox \
--deterministic-time 1700000000000 --deterministic-seed 42
# Record a live CDP session to JSONL
foxbridge --binary /usr/bin/firefox --record ./trace.jsonl
# Replay a captured CDP session without launching Firefox
foxbridge replay --recording ./trace.jsonl
# Print a protocol coverage report
foxbridge doctorfoxbridge doctor is now the source of truth for protocol coverage:
foxbridge doctor
foxbridge doctor --format jsonCurrent snapshot on main from foxbridge doctor:
662upstream CDP methods in the bundled protocol snapshot89implemented methods203stubbed compatibility methods370missing methods8foxbridge-only extensions
The strongest covered areas today are DOM, Page, Target, Network, Fetch, Runtime, Input, Emulation, IO, and Performance. Stubbed domains still exist for compatibility, but foxbridge should no longer be described as “13 fully implemented CDP domains”.
| Firefox Event | CDP Event |
|---|---|
Browser.attachedToTarget |
Target.attachedToTarget (tab + page dual session) |
Browser.detachedFromTarget |
Target.targetDestroyed |
Page.navigationCommitted |
Page.frameNavigated + lifecycle events |
Page.eventFired(load) |
Page.loadEventFired + Page.frameStoppedLoading |
Page.eventFired(DOMContentLoaded) |
Page.domContentEventFired |
Runtime.executionContextCreated |
Runtime.executionContextCreated |
Runtime.console |
Runtime.consoleAPICalled |
Page.dialogOpened |
Page.javascriptDialogOpening |
Network.requestWillBeSent |
Network.requestWillBeSent |
Network.responseReceived |
Network.responseReceived |
Browser.requestIntercepted |
Fetch.requestPaused |
# Browser info
curl http://localhost:9222/json/version
# Active page targets
curl http://localhost:9222/json/listfoxbridge implements the Backend interface — swap between Juggler and BiDi without changing the bridge layer:
type Backend interface {
Call(sessionID, method string, params json.RawMessage) (json.RawMessage, error)
Subscribe(event string, handler func(sessionID string, params json.RawMessage))
Close() error
}- Juggler (
--backend juggler): Pipe FD 3/4 transport, null-byte JSON framing. Direct protocol match with Playwright. Default and most battle-tested. - BiDi (
--backend bidi): WebSocket transport, W3C standard. The BiDi client internally translates Juggler-style calls to BiDi equivalents so the bridge layer works unchanged.
foxbridge can run as an embedded CDP server inside a host process sharing a single Firefox instance:
Host -> Firefox (single process) -> Juggler pipe -> juggler.Client
├── Direct Juggler calls
└── Embedded foxbridge CDP server on :9222
└── CDP clients connect via cdpUrl -> same Firefox contexts
No separate process needed — the host imports foxbridge as a Go library and starts the CDP server in-process. CDP clients connect to Firefox through the embedded bridge.
| Flag | Default | Description |
|---|---|---|
--port |
9222 | CDP WebSocket port |
--socket |
— | Unix-domain socket path for the CDP HTTP/WebSocket server |
--binary |
auto-detect | Firefox binary path |
--headless |
false | Run headless |
--profile |
— | Firefox profile directory |
--backend |
juggler | Backend: juggler or bidi |
--bidi-url |
— | Connect to existing BiDi endpoint |
--bidi-port |
9223 | BiDi port when auto-launching Firefox |
--record |
— | Path to write a JSONL CDP wire capture |
--deterministic-time |
0 | Base epoch in milliseconds for deterministic Date.now / performance.now injection |
--deterministic-seed |
0 | Seed for deterministic Math.random / crypto.getRandomValues injection |
When --socket is set, foxbridge listens on a Unix domain socket instead of binding a TCP port. Discovery endpoints are still available over HTTP, and the browser-level WebSocket URL remains /devtools/browser/foxbridge; clients must dial that URL using their library's Unix-socket transport or socketPath option.
When either deterministic flag is set, foxbridge injects a deterministic runtime prelude into page sessions so Date.now, performance.now, Math.random, and crypto.getRandomValues become repeatable across runs.
When --record is set, foxbridge writes every inbound and outbound CDP frame for the active session to JSONL. The resulting file can be replayed later with foxbridge replay --recording /path/to/trace.jsonl to reproduce client traffic without launching a browser.
foxbridge has comprehensive test coverage:
-
Go unit tests — all passing with race detector (
go test -race ./...) -
chromedp end-to-end suite — drives a live Firefox through the CDP proxy (
go run test/chromedp/main.goagainst a running foxbridge) -
Firefox version matrix — smoke-tests the BiDi backend against archived Firefox releases (fully green 124–152):
# needs docker + network access to ghcr.io test/firefox-versions.sh # default list (129 … 152) test/firefox-versions.sh 129 140 152 # specific versions
For each version it boots
ghcr.io/techarohq/gubal/firefox:<major>with the freshly built foxbridge binary and runs a per-command driver (test/chromedp/drive/main.go) that exercises each distinct BiDi command.
go test -race ./...CI runs on every push via GitHub Actions (build + vet + race-detected tests).
MIT