
Camoufox
- 5 repo stars
- Updated June 3, 2026
- 9prodhi/camoufox-rs
camoufox-rs is a Rust library and CLI that controls a Camoufox browser through the Firefox Juggler protocol.
About
camoufox-rs is a pure Rust client for controlling a Camoufox browser through the Firefox Juggler protocol. It provides a synchronous library API over the -juggler-pipe transport with Browser, MainFrame, Network, Runtime, and Heap wrappers, plus an optional CLI (built with --features cli) that runs a Unix-socket daemon for multi-instance management. The CLI can launch instances, create pages, navigate, evaluate JavaScript, export cookies, and take screenshots. It is Unix-first and requires Rust 1.70+ and a Camoufox binary on disk.
- Pure Rust client controlling Camoufox over the Firefox Juggler protocol
- Optional CLI with a Unix-socket daemon for multi-instance management
- Supports launch, navigate, evaluate, cookie export, and screenshots
Camoufox by the numbers
- Data as of Jul 7, 2026 (Skillselion catalog sync)
camoufox capabilities & compatibility
Free open-source crate; requires a Camoufox binary on disk.
- Capabilities
- browser automation · web scraping
- Use cases
- web scraping
- Platforms
- Linux · macOS
- Pricing
- Free
/plugin marketplace add 9prodhi/camoufox-rs/plugin install camoufox@CamoufoxAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| repo stars | ★ 5 |
|---|---|
| Last updated | June 3, 2026 |
| Repository | 9prodhi/camoufox-rs ↗ |
What it does
A developer uses this when they need to control or automate a Camoufox (Firefox-based) browser programmatically from Rust over the Juggler protocol.
Who is it for?
Programmatically driving a Camoufox browser from Rust over the Juggler protocol.
Skip if: Windows-only setups or projects without a Camoufox binary, given its Unix-first design.
When should I use this skill?
A developer needs Rust-native automation of a Camoufox/Firefox browser.
What you get
A Rust program (or CLI daemon) can launch, navigate, evaluate, and screenshot a Camoufox browser.
By the numbers
- requires Rust 1.70+
- Juggler domains: Browser, MainFrame, Network, Runtime, Heap
README.md
camoufox-rs
Pure Rust client for controlling Camoufox through the Firefox Juggler protocol.
This crate implements the full stack needed to automate a Camoufox browser process over the -juggler-pipe transport: process launch, null-delimited JSON framing, protocol request/response/event routing, and ergonomic Browser / BrowserContext / MainFrame wrappers.
Current Scope
- Library crate with a synchronous API for Juggler domains (
Browser,MainFrame,Network,Runtime,Heap) - Optional CLI (
--features cli) with a Unix socket daemon for multi-instance management - Unix-first implementation (Linux/macOS style process + fd pipe model)
- Protocol reference docs in-repo:
docs/PROTOCOL.mddocs/UNDERSTANDING.md
Requirements
- Rust 1.70+ (see
Cargo.toml) - Unix-like OS for full functionality (process spawning + Unix sockets)
- Camoufox binary available on disk
The CLI daemon resolves the Camoufox binary in this order:
--executable <path>passed tolaunch- the
CAMOUFOX_BINenvironment variable $HOME/.cache/camoufox/camoufox(falling back to/root/.cache/camoufox/camoufoxwhenHOMEis unset)
Build
Library only:
cargo build
CLI binary:
cargo build --features cli --bin camoufox
CLI Quick Start
The CLI uses a daemon process and newline-delimited JSON over a Unix domain socket.
Start daemon (run in a dedicated shell):
cargo run --features cli --bin camoufox -- serve --foreground
Launch an instance:
cargo run --features cli --bin camoufox -- launch
Create a page:
cargo run --features cli --bin camoufox -- new-page <instance_id>
Navigate and evaluate:
cargo run --features cli --bin camoufox -- navigate <instance_id> <page_id> https://example.com
cargo run --features cli --bin camoufox -- evaluate <instance_id> <page_id> "document.title"
Navigate with wait-until and status_code:
# Block until the load event fires (bounded by --timeout seconds):
cargo run --features cli --bin camoufox -- navigate <instance_id> <page_id> https://example.com \
--wait-until load --timeout 30
# Or wait only until DOMContentLoaded:
cargo run --features cli --bin camoufox -- navigate <instance_id> <page_id> https://example.com \
--wait-until domcontentloaded --timeout 15
# --wait-until accepts: load, domcontentloaded. Any other value is an error.
# navigate always succeeds even on 4xx/5xx responses.
# --json output includes status_code (final main-document HTTP status after following
# all redirects; null if uncapturable, e.g. about: pages or navigation errors):
# { "ok": true, "data": { "navigation_id": "...", "status_code": 200 } }
Export the session cookie jar:
# Export all cookies for all instances (includes HttpOnly cookies):
cargo run --features cli --bin camoufox -- cookies <instance_id>
# --json returns full cookie objects (name, value, domain, path, httpOnly, secure, ...):
cargo run --features cli --bin camoufox -- --json cookies <instance_id>
# The exported jar can drive host-side fetches without in-session XHR:
# curl --cookie "name=value" https://example.com/gated-endpoint
Take a screenshot:
cargo run --features cli --bin camoufox -- screenshot <instance_id> <page_id> --format png -o /tmp/example.png
Inspect and stop:
cargo run --features cli --bin camoufox -- list
cargo run --features cli --bin camoufox -- stop <instance_id>
cargo run --features cli --bin camoufox -- shutdown
JSON output mode is available for all commands:
cargo run --features cli --bin camoufox -- --json list
Socket resolution:
--socket <path>to override- else
$XDG_RUNTIME_DIR/camoufox/daemon.sock - else
/tmp/camoufox-<uid>/daemon.sock
Library Bootstrap Example
The low-level lifecycle is:
- Build
LaunchConfig - Spawn process (
process::unix::spawn) - Wait readiness sentinel on stderr
- Build
PipeTransport - Build
Connection+ root session Browser::connect(...)
use std::path::PathBuf;
use camoufox::api::{Browser, BrowserOptions, ContextOptions};
use camoufox::config::LaunchConfig;
use camoufox::process;
use camoufox::protocol::client::Connection;
use camoufox::transport::pipe::PipeTransport;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let profile_dir = std::env::temp_dir().join("camoufox-rs-profile");
std::fs::create_dir_all(&profile_dir)?;
let config = LaunchConfig {
executable: PathBuf::from("/root/.cache/camoufox/camoufox"),
profile_dir: Some(profile_dir),
headless: true,
..Default::default()
};
let mut launched = process::unix::spawn(&config)?;
process::readiness::wait_for_ready(&mut launched.child, config.timeout)?;
let transport = PipeTransport::new(launched.command_pipe, launched.response_pipe);
let conn = Connection::new(Box::new(transport));
let root = conn.root_session();
let browser = Browser::connect(conn, root, BrowserOptions::default())?;
let _context = browser.new_context(ContextOptions::default())?;
browser.close()?;
Ok(())
}
Pages via MainFrame
BrowserContext::new_main_frame() returns a fully wired MainFrame — a page
handle that is structurally pinned to the top frame. The call blocks until
the page target, top frame, and main-world execution context are all resolved
from authoritative protocol responses, so there is no manual session or
execution-context wiring to do.
This is the fix for the cross-origin-iframe attach bug: on sites that embed an
early out-of-process iframe (e.g. an ad pixel), the old handle could bind to the
iframe instead of the page — evaluate then ran in the wrong document.
new_main_frame() applies three filters so it can only ever resolve to the real
top frame:
- Layer 1 — accept only
Browser.attachedToTargetevents wheretargetInfo.type == "page" - Layer 2 — accept only the top frame's
Page.frameAttached(emptyparentFrameId) - Layer 3 — accept only the main-world
Runtime.executionContextCreatedwhoseauxData.frameIdmatches the top frame
use std::time::Duration;
// ...continuing from the bootstrap example, after `Browser::connect`:
let context = browser.new_context(ContextOptions::default())?;
// One call — no manual attachedToTarget / frameAttached / executionContext wiring:
let main_frame = context.new_main_frame()?;
// navigate(url, NavigateOptions, timeout) -> NavigateOutcome { nav_id, status_code }.
// status_code is the final main-document HTTP status after following redirects.
let outcome = main_frame.navigate("https://example.com", Default::default(), Duration::from_secs(30))?;
println!("status: {:?}", outcome.status_code);
// evaluate(expr, timeout) — the cached execution context is maintained internally,
// so no execution-context id is threaded through.
let title = main_frame.evaluate("document.title", Duration::from_secs(15))?;
println!("title: {title}");
A complete, runnable version is in examples/web_browse.rs:
cargo run --example web_browse -- --url https://example.com
End-to-end wiring is also exercised in tests/integration.rs and src/cli/instance.rs.
Reliability
- Every protocol request is bounded —
Client::sendenforces a default 60s deadline, andnavigate/evaluate/screenshotaccept an explicittimeout(CLI:--timeout <seconds>), so a stuck call can no longer hang the daemon. - Navigations that the browser diverts into a download (e.g. a
Content-Disposition: attachmentURL) are detected viaBrowser.downloadCreatedand surfaced promptly as aNavigationBecameDownloaderror instead of blocking forever waiting for a navigation response that never arrives.
Architecture
Core layers (top to bottom):
api/: high-levelBrowser,BrowserContext,MainFrameprotocol/: request IDs, pending map, session state, event router, reader threadtransport/: transport traits + Unix pipe transportcodec/: null-byte-delimited JSON framing (NulJsonCodec)process/: spawn/readiness/lifecycle around Camoufox child processcli/(feature-gated): daemon + command dispatch over Unix socketcompat/: Camoufox detection/version capability checksobs/: protocol logging helpers
Testing
Unit tests:
cargo test
Integration tests against a real Camoufox binary are ignored by default:
cargo test --test integration -- --ignored --test-threads=1
Integration tests resolve the binary the same way as the daemon: CAMOUFOX_BIN,
else $HOME/.cache/camoufox/camoufox. Override per run with:
CAMOUFOX_BIN=/path/to/camoufox cargo test --test integration -- --ignored --test-threads=1
Observability
Use log + env_logger filters to inspect protocol behavior:
RUST_LOG=camoufox=trace cargo test
obs::ProtocolLogger formats command, response, and event traces with bounded payload previews.
Known Limitations
- Windows pipe transport is not implemented (
src/transport/pipe/windows.rshard errors at compile time) - API is synchronous/blocking today (no async runtime integration)
MainFrameis top-frame only; there is no public API for operating on sub-frames- CLI daemon uses in-memory instance state only
License
MIT (see crate metadata in Cargo.toml).
Related skills
FAQ
What are the requirements?
It needs Rust 1.70+, a Unix-like OS for full functionality, and a Camoufox binary on disk.
What can the CLI do?
It can launch instances, create pages, navigate, evaluate JavaScript, export cookies, and take screenshots.