6 releases (breaking)
| new 0.9.1 | May 16, 2026 |
|---|---|
| 0.9.0 | May 16, 2026 |
| 0.8.0 | May 16, 2026 |
| 0.7.0 | May 16, 2026 |
| 0.2.0 | May 13, 2026 |
#1658 in Web programming
Used in 3 crates
(2 directly)
71KB
1.5K
SLoC
π OxiBrowser
The headless browser built in pure Rust for AI agents.
Not a Chromium fork. Not a C++ wrapper. A browser engine written from scratch in Rust, designed from day one for automation, web scraping, and AI-driven workflows.
Report Bug Β· Request Feature Β· Read the Docs Β· Discord
| 21 MB Single static binary |
~50 ms Cold start time |
~8 MB Base memory |
279 tests Full coverage |
Zero C deps Pure Rust |
| OxiBrowser | Headless Chrome | Lightpanda |
|---|---|---|
| 21 MB binary | ~400 MB install | ~80 MB binary |
| ~8 MB RAM base | ~200 MB RAM base | ~30 MB RAM base |
| ~50 ms startup | ~800 ms startup | ~10 ms startup |
| Pure Rust (boa) | C++ (V8) | Zig (V8) |
| MIT License | BSD / ToS | AGPL-3.0 |
β¨ Why OxiBrowser?
You're building AI agents that need to browse the web. You don't need a full browser with GPU rendering, audio output, and extension support. You need something fast, small, and programmable.
OxiBrowser is built for exactly that use case:
- π€ AI-Agent First β Native
OXICDP domain withgetMarkdown(),getPageInfo(), and text-first rendering - β‘ Blazing Fast β Cold starts in ~50ms, no Chromium overhead, no Node.js required
- π¦ Pure Rust β Zero C dependencies.
boa_enginefor JS (no V8). Single static binary. Memory-safe. - π CDP Compatible β Puppeteer, Playwright, and any Chrome DevTools Protocol client works out of the box
- π‘οΈ Secure by Default β SSRF protection with CIDR blocking,
robots.txtrespect, no sandbox escape surface - π¦ Tiny Footprint β 21 MB binary, ~8 MB base memory. Run 100 instances without breaking a sweat
π Quick Start
Install
Cargo (all platforms)
cargo install oxibrowser
Build from source
git clone https://github.com/a7garden/oxibrowser.git
cd oxibrowser
cargo build --release
# Binary at ./target/release/oxibrowser
Use as a library
# Cargo.toml
[dependencies]
oxibrowser-core = "0.7"
Fetch a page
oxibrowser fetch https://example.com
Start CDP server
oxibrowser serve --port 9222
Then connect with Puppeteer:
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://127.0.0.1:9222',
});
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com');
// Get markdown β OxiBrowser's AI-native feature
const md = await page.evaluate(() => {
// OXI domain available in evaluate context
});
console.log(await page.title());
await browser.close();
Rust API
use oxibrowser_core::Browser;
use oxibrowser_core::config::BrowserConfig;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let browser = Browser::new(BrowserConfig::default()).await?;
let session = browser.new_session().await?;
session.navigate("https://example.com").await?;
let title = session.evaluate("document.title").await?;
println!("Title: {:?}", title);
Ok(())
}
π Architecture
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Puppeteer / Playwright / Rust CDP β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β CDP WebSocket
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CDP Server (10 domains) β
β Browser Β· DOM Β· Fetch Β· Input Β· Network β
β OXI Β· Page Β· Runtime Β· Target β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Browser β Session β Page β Frame β
ββββββββββββ¬βββββββββββ¬βββββββββββββββ¬ββββββββββββββββββ€
β WebAPI β Network β JS Runtime β CSS Rendering β
β DOM β HTTP β boa_engine β PNG screenshot β
β Tree β Cookies β ES2024+ β ASCII/Unicode β
β Storage β SSRF β persistent β textβimage β
ββββββββββββ΄βββββββββββ΄βββββββββββββββ΄ββββββββββββββββββ€
β html5ever Β· encoding_rs Β· reqwest Β· image Β· boa β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Crate Structure
| Crate | Lines | Purpose |
|---|---|---|
oxibrowser |
1,233 | Binary + CLI (fetch, serve, version) |
oxibrowser-core |
12,953 | Browser engine: Session, Page, Frame, JS Runtime |
oxibrowser-cdp |
4,392 | CDP WebSocket server with 10 domain handlers |
oxibrowser-webapi |
1,549 | DOM tree, CSS selectors, Markdown conversion |
| Total | 20,127 |
π Features
JavaScript Runtime (ES2024+)
Powered by boa_engine β pure Rust, no V8 dependency:
| Web API | Status |
|---|---|
document.querySelector / querySelectorAll |
β Full |
document.createElement / createTextNode |
β Full |
element.appendChild / removeChild / insertBefore |
β Full |
element.getAttribute / setAttribute / removeAttribute |
β Full |
element.cloneNode / remove() |
β Full |
element.style (CSSStyleDeclaration) |
β Property accessor |
element.classList (DOMTokenList) |
β Property accessor |
element.textContent / innerHTML |
β Read/Write |
element.addEventListener / dispatchEvent |
β Full |
element.click() |
β With event handlers |
fetch() |
β Full (channel bridge) |
XMLHttpRequest |
β Full with callbacks |
localStorage |
β Persistent |
MutationObserver |
β observe/disconnect/takeRecords |
setTimeout / setInterval |
β TokioJobQueue |
console.log/warn/error |
β With formatting |
URL / URLSearchParams |
β Full |
crypto.getRandomValues |
β Pseudo-random |
TextEncoder / TextDecoder |
β UTF-8 |
atob / btoa |
β Base64 |
requestAnimationFrame |
β Polyfill |
CDP Protocol (Chrome DevTools Protocol)
10 domain handlers β Puppeteer and Playwright compatible:
| Domain | Key Methods |
|---|---|
| Browser | getVersion, close |
| DOM | getDocument, describeNode, querySelector, querySelectorAll |
| Fetch | enable/disable, continueRequest, failRequest, fulfillRequest, getResponseBody |
| Input | dispatchKeyEvent, dispatchMouseEvent, insertText |
| Network | enable/disable, setExtraHTTPHeaders, getResponseBody |
| OXI π€ | getMarkdown, getPageInfo β AI-native extensions |
| Page | navigate, captureScreenshot, getFrameTree, getTitle |
| Runtime | evaluate, callFunctionOn, enable, consoleAPICalled |
| Target | getTargets, attachToTarget, detachFromTarget |
OXI Domain β Built for AI Agents
The OXI CDP domain provides AI-optimized APIs that no other browser offers:
import websockets, json, asyncio
async def ai_scrape():
ws = await websockets.connect('ws://localhost:9222/ws')
# Navigate
await ws.send(json.dumps({
"id": 1, "method": "Page.navigate",
"params": {"url": "https://news.ycombinator.com"}
}))
await asyncio.sleep(2)
# Get clean markdown β perfect for LLM ingestion
await ws.send(json.dumps({
"id": 2, "method": "OXI.getMarkdown"
}))
resp = json.loads(await ws.recv())
print(resp['result']['markdown']) # Clean markdown output
# Get structured page info
await ws.send(json.dumps({
"id": 3, "method": "OXI.getPageInfo"
}))
info = json.loads(await ws.recv())
print(info['result']) # title, url, status, content-type, etc.
Network Layer
| Feature | Description |
|---|---|
| HTTP Client | reqwest with cookie persistence, redirect following |
| Cookie Jar | Domain-scoped cookie storage with Set-Cookie parsing |
| SSRF Protection | CIDR blocking for private network ranges |
| robots.txt | RFC 9309 compliant parser, --obey-robots flag |
| Network Interception | Pause, modify, or block any request via Fetch domain |
| Custom Headers | Per-session and per-request header injection |
| Charset Detection | encoding_rs for automatic charset detection and conversion |
CSS Text Rendering
- ASCII/Unicode text output β Render DOM to readable text with proper indentation
- Markdown conversion β Full HTMLβMarkdown with heading, link, and list support
- PNG screenshots β Built-in 8Γ16 bitmap font, renders text content as images
- No external dependencies β Font data embedded in binary
π§ͺ Testing
# Run all 279 tests
cargo test --workspace
# E2E CDP tests (23 tests with real WebSocket)
cargo test -p oxibrowser-cdp
# Integration tests (real websites, --ignored)
cargo test --workspace -- --ignored
# Puppeteer smoke tests
cargo test -p oxibrowser --test smoke
π CLI Reference
oxibrowser 0.7.0
Headless browser with CDP support
USAGE:
oxibrowser <COMMAND>
COMMANDS:
fetch Fetch and render a URL
serve Start CDP WebSocket server
version Print version information
FETCH OPTIONS:
<URL> URL to fetch
--dump <FORMAT> Output format: text, html, markdown [default: text]
SERVE OPTIONS:
--host <HOST> Bind address [default: 127.0.0.1]
--port <PORT> Bind port [default: 9222]
--obey-robots Respect robots.txt
--log-level <LEVEL> Log level: trace, debug, info, warn, error [default: info]
π§ Advanced Usage
Custom Browser Configuration
use oxibrowser_core::{Browser, config::BrowserConfig};
use std::time::Duration;
let config = BrowserConfig {
user_agent: "MyBot/1.0".to_string(),
timeout: Duration::from_secs(30),
obey_robots: true,
max_redirects: 10,
..Default::default()
};
let browser = Browser::new(config).await?;
Request Interception
// With Puppeteer
const client = await page.target().createCDPSession();
await client.send('Fetch.enable', {
patterns: [{ urlPattern: '*ads*' }]
});
client.on('Fetch.requestPaused', async ({ requestId }) => {
// Block ad requests
await client.send('Fetch.failRequest', {
requestId,
reason: 'BlockedByClient'
});
});
Screenshot Capture
// PNG screenshot via CDP
const { data } = await client.send('Page.captureScreenshot', {
format: 'png'
});
// data is base64-encoded PNG
π€ Contributing
Contributions are welcome! Whether it's:
- π Bug reports β Open an issue
- π‘ Feature requests β Start a discussion
- π§ Pull requests β Fork, branch, PR. All PRs need passing tests.
- π Documentation β Fix typos, add examples, improve guides
Development Setup
git clone https://github.com/a7garden/oxibrowser.git
cd oxibrowser
cargo build
cargo test --workspace
cargo clippy --workspace -- -D warnings
π License
OxiBrowser is licensed under the MIT License.
π Acknowledgments
- Lightpanda β Architecture inspiration (Browser β Session β Page β Frame hierarchy)
- boa_engine β Pure Rust JavaScript engine (ES2024+)
- html5ever β HTML parser from the Servo project
- reqwest β Ergonomic HTTP client for Rust
- tokio β Async runtime powering the entire networking stack
Made with π¦ in Rust
Dependencies
~6β9.5MB
~100K SLoC