ff-rdp
A fast Rust CLI for the Firefox Remote Debugging Protocol. Communicates directly over TCP with Firefox's built-in debugger for minimal latency.
Installation
Homebrew (macOS & Linux)
Scoop (Windows)
scoop bucket add ff-rdp https://github.com/ractive/scoop-ff-rdp
scoop install ff-rdp
winget (Windows)
winget install ractive.ff-rdp
Cargo (from crates.io)
Manual download
Download pre-built binaries from the GitHub Releases page. Binaries are available for Linux (x86_64, ARM64, glibc and musl), macOS (Apple Silicon), and Windows (x86_64, ARM64).
First contact (for AI agents)
If anything goes wrong, run ff-rdp doctor first — it pinpoints connection,
port, and version issues in one shot. The probes are:
- Daemon registry — is a daemon running and reachable?
- Port owner — who is listening on
--port(PID, process, uptime)? - RDP handshake — can we receive a Firefox greeting?
- Tabs — how many tabs are exposed by the connected target?
- Firefox version compatibility — within the tested range?
A typical first-time session looks like:
When ff-rdp launch finds the requested port already in use it now fails
loudly with the listener's PID and a hint pointing at doctor. Every
known-failure-mode error in ff-rdp ends with a hint: line that names the
next concrete command to run — connection-related ones name doctor first.
Requirements
- Firefox with remote debugging enabled:
- Rust toolchain (for building from source)
Build
Usage
ff-rdp [OPTIONS] <COMMAND>
Commands:
tabs List open browser tabs
navigate Navigate to a URL (with --with-network for traffic capture)
eval Evaluate JavaScript (positional, --file <PATH>, or --stdin)
dom Query DOM elements by CSS selector (--outer-html, --inner-html, --text, --attrs)
page-text Extract visible page text (document.body.innerText)
computed Get computed CSS styles for elements (--prop <NAME>, --all)
console Read console messages (--level, --pattern; output includes summary with totals)
network Show network requests (with --filter, --method filters)
perf Query Performance API entries and Core Web Vitals
click Click an element matching a CSS selector
type Type text into an input element matching a CSS selector
wait Wait for a condition to become true (polls every 100ms)
cookies List cookies via StorageActor (includes httpOnly, secure, sameSite)
storage Read web storage (localStorage or sessionStorage)
screenshot Capture a screenshot (--full-page, --viewport-height N, --base64)
inspect Inspect a remote JavaScript object by its grip actor ID
sources List JavaScript/WASM sources loaded on the page
launch Launch Firefox with remote debugging enabled
doctor Diagnose the connection (daemon, port, handshake, tabs, version)
reload Reload the page (--wait-idle blocks until network is idle)
back Go back in history
forward Go forward in history
scroll Scroll the page or an element (to/by/container/until/text)
responsive Test layout across viewport widths
a11y Inspect accessibility tree and check WCAG contrast
geometry Get element bounding rects, visibility, overlap detection
snapshot Dump structured page snapshot for LLM consumption
styles Inspect CSS applied rules or box model layout
Options:
--host <HOST> Firefox debug server host [default: localhost]
--port <PORT> Firefox debug server port [default: 6000]
--tab <TAB> Target tab by index (1-based) or URL substring
--tab-id <TAB_ID> Target tab by exact actor ID
--jq <JQ> jq filter expression applied to output
--timeout <TIMEOUT> Operation timeout in milliseconds [default: 5000]
--no-daemon Don't use or start a daemon (direct Firefox connection)
--daemon-timeout <SECS> Daemon idle timeout in seconds [default: 300]
--allow-unsafe-urls Allow javascript: and data: URLs in navigate
All output is JSON with a standard envelope (results, total, meta). Use --jq to filter:
# List tab URLs
# Navigate to a URL
# Evaluate JavaScript and extract the result
# Eval from a file (avoids shell quoting issues with ?. or template literals)
# Eval from stdin
|
# Target a specific tab by URL substring
# Query DOM elements by CSS selector (default: outerHTML)
# Get text content of matching elements
# Get element attributes as JSON
# Extract all visible page text
# Count characters in page text
# Read console messages (errors only); output includes summary.total, summary.shown, summary.by_level
# Filter console messages by pattern; limit to 20 results
# Check how many messages matched vs how many were shown
# Show network requests
# Filter network by URL substring
# Filter network by HTTP method
# Navigate and capture all network traffic in one shot
# Find failed requests during navigation
# Query Performance API resource timing entries (default: --type resource)
# Page load waterfall (DNS, TLS, TTFB, DOM timings)
# First Paint and First Contentful Paint timestamps
# Largest Contentful Paint
# Cumulative Layout Shift entries
# Long tasks (>50ms)
# Filter resource entries by URL substring
# Core Web Vitals summary with ratings (LCP, CLS, TBT, FCP, TTFB)
# Extract a single metric
# Click a button
# Type into an input (clear first with --clear)
# Wait for an element to appear (default timeout: 5000ms)
# Wait for text to appear on the page
# Wait for a JavaScript expression to become truthy
# List cookies
# Filter cookies by name
# Dump all localStorage
# Get a specific sessionStorage key
# Capture a screenshot (saves PNG)
# Full-page screenshot (captures entire scrollable document)
# Screenshot at explicit height
# Get computed color for an element
# Get all non-default computed styles for a selector
# Get the full resolved style object
# Launch Firefox with debugging enabled
# Launch headless Firefox with temporary profile
# Launch with a specific profile and debug port
# Inspect a remote object grip (from eval output)
# Recursive inspection (depth 2)
# List all loaded JavaScript sources
# Filter sources by URL substring
# Filter sources by regex pattern
# Reload, go back, go forward
# Reload and wait until network is idle (replaces sleep)
Daemon Mode
By default, the first CLI invocation auto-starts a background daemon that holds a persistent Firefox RDP connection and buffers watcher events. Subsequent invocations connect through the daemon for faster execution and cross-command workflows.
How it works:
- First
ff-rdpcall spawns a daemon process (ff-rdp _daemon) in the background - The daemon connects to Firefox, subscribes to watcher resources (network, console, errors), and listens on a random TCP loopback port
- Subsequent CLI calls connect to the daemon instead of Firefox directly
- The daemon transparently proxies RDP frames and also exposes a
"daemon"virtual actor for draining buffered events - Daemon exits automatically after 5 minutes of inactivity (configurable via
--daemon-timeout)
Cross-command workflows (enabled by daemon):
# Navigate, then inspect network traffic as separate commands
# Object grips from eval survive across invocations
Disabling the daemon:
# Connect directly to Firefox (original behavior)
Registry and logs:
- Registry file:
~/.ff-rdp/daemon.json(PID, port, Firefox target) - Log file:
~/.ff-rdp/daemon.log - Stale registry files are cleaned up automatically when the daemon PID is dead
Troubleshooting:
- If the daemon seems stuck, delete
~/.ff-rdp/daemon.jsonto force a fresh start - Use
--no-daemonto bypass the daemon and test direct connectivity - Check
~/.ff-rdp/daemon.logfor daemon-side errors
Security
ff-rdp has the same power as Firefox DevTools — it can read httpOnly cookies, execute arbitrary JavaScript, capture screenshots, and navigate to URLs. The security model is "same as opening DevTools": the user is the operator.
Transport: Firefox RDP uses plaintext TCP with no TLS. By default ff-rdp connects to localhost only. For remote debugging, use SSH tunneling (ssh -L 6000:localhost:6000 remote-host) rather than exposing the debug port directly.
URL validation: The navigate command rejects javascript: and data: URLs by default to prevent accidental code execution in the page context. Allowed schemes are http:, https:, file:, and about:. Use --allow-unsafe-urls to bypass this check if needed.
Daemon trust model: The daemon listens on 127.0.0.1 (loopback only). Any local process can connect and send RDP commands through it — the same trust boundary as Firefox DevTools. The registry file (~/.ff-rdp/daemon.json) is created with owner-only permissions (0600 on Unix).
Regex limits: The --pattern flag (used by console and sources commands) applies a 1 MiB NFA size limit to prevent denial-of-service from pathological regular expressions.
Not designed for untrusted networks. Do not expose the Firefox debug port to the network. All RDP traffic (page content, cookies, eval results) is transmitted in plaintext.
Architecture
- ff-rdp-core — Protocol library: blocking TCP transport, length-prefixed JSON framing, typed errors
- ff-rdp-cli — CLI binary: clap args, jq output pipeline, command dispatch, daemon proxy
Releasing
- Bump the version in
Cargo.toml - Commit:
git commit -am "Bump version to X.Y.Z" - Create a GitHub release with tag
vX.Y.Z(must matchCargo.toml)
The release workflow automatically builds binaries for all platforms, publishes to crates.io, and updates Homebrew/Scoop/winget.
License
MIT