obscura

h4ckf0r0dayApache-2.0活跃维护 有专家点评

用 Rust 写的无头浏览器,专为 AI Agent 和网页抓取设计,自带反检测能力,能绕过网站对自动化浏览器的识别。

The headless browser for AI agents and web scraping

v0.2.6发布于 2026-10-31 20:59 UTC32,292star +1,690 / 7 天2,486fork同步于 2026-11-11 05:20 UTC

专家点评

上手难度:进阶更新于 2026-11-11

用 Rust 重写的无头浏览器引擎,在爬虫和 Agent 链路里顶替 headless Chrome,CDP 协议兼容是它的接入点

适用场景

  • Puppeteer/Playwright 脚本迁移:现有代码走 CDP,改连接地址就能指向 obscura,不用重写选择器和等待逻辑,省掉换框架的迁移成本
  • 目标站点有指纹检测的抓取:stealth 能力打包在引擎里,不用再维护 puppeteer-extra-plugin-stealth 那套补丁链,减少版本升级时补丁失效的排查
  • Agent 的页面读取步骤:fetch 子命令直接 dump HTML/文本/链接/资源列表,不需要起浏览器进程再走一遍 CDP 握手,单次取页面比完整浏览器轻
  • 需要截图/PDF 产物的离线批处理:原生渲染路径不依赖外部 Chrome 二进制,容器里少一层依赖

踩坑

  • 下载错发布包:默认包不含 stealth,带 -stealth 后缀的才是反检测版本,装完发现指纹没变先查这个
  • PDF 导出:issue 里 Page.printToPDF 未实现和输出栅格化 JPEG(无文本层、无嵌入字体、800px 上限)被反复提出,需要可搜索文本的 PDF 归档场景现在会撞上
  • 从源码构建:V8 从源码编译,首次约 5 分钟,stealth 版还要 CMake/Clang/libclang,CI 里没预装这些工具链会直接卡在构建阶段
  • Docker 暴露端口:-p 9222:9222 会绑到所有网卡,README 自己要求改绑 127.0.0.1 并设 OBSCURA_CDP_TOKEN,照抄示例命令等于把 CDP 控制面开到公网

同类对比

要成熟生态和完整 CDP 覆盖(PDF、复杂下载、DevTools 调试)还是用 headless Chrome/Playwright;只要 HTTP 层抓取、不需要执行 JS 的话,直接用 reqwest 加解析库比拖一个浏览器引擎便宜得多

怎么上手

由 README 提炼,执行前请确认
curl -LO https://github.com/h4ckf0r0day/obscura/releases/latest/download/obscura-x86_64-linux.tar.gz
tar xzf obscura-x86_64-linux.tar.gz
./obscura fetch https://example.com --eval "document.title"
硬件要求按 README 说明

README 未说明显存或 GPU 要求;提供 Linux x86_64、aarch64、macOS Apple Silicon、Intel 和 Windows x86_64 的预编译二进制,Docker 镜像基于 distroless,未提及 CPU 运行限制。

支持 Apple Silicon有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

用 Rust 写的无头浏览器引擎,给 AI Agent 和爬虫替代 headless Chrome

用 Rust 写的无头浏览器,专为 AI Agent 和网页抓取设计,自带反检测能力,能绕过网站对自动化浏览器的识别。

核心能力

  • 用 V8 执行真实 JavaScript 渲染页面
  • 兼容 Chrome DevTools Protocol,可直接替换 Puppeteer 和 Playwright
  • 内置反检测能力,无需额外插件
  • 支持截图、录屏和导出 PDF 的原生渲染
  • 提供 fetch 命令,可 dump HTML、文本、链接和资源列表

容易踩的坑

从源码构建时,首次编译约需 5 分钟,因为 V8 要从源码编译;stealth 版本还需要 CMake、Clang 和 libclang/LLVM 开发库。 发布包按后缀区分渲染和 stealth 能力:默认包不含 stealth,需要 stealth 时必须手动下载带 -stealth 后缀的包。 Docker 用 -p 9222:9222 会把端口暴露到所有网卡,README 建议绑定到 127.0.0.1,并设置 OBSCURA_CDP_TOKEN。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.2.6
  1. v0.2.6最新正式版2026-10-31 · 2026-10-31 20:59 UTC

    v0.2.6

    This release improves request interception, frame navigation, CDP responsiveness, and JavaScript startup. It also fixes browser API behavior, updates security-sensitive dependencies, and strengthens performance regression checks. 117 commits since v0.2.5.

    Highlights

    • Main-document requests can be intercepted before transport. Playwright and Puppeteer can fulfill navigation requests directly. Binary bodies, repeated cookies, continuation overrides, redirects, and request completion retain their expected behavior (#1384).
    • CDP remains responsive while evaluations await promises. Other commands and interception replies can proceed while asynchronous evaluations settle. Script evaluation runs directly through V8 and preserves global lexical bindings (#1310).
    • Frame navigation stays in the correct document. Child-frame Location methods and named form targets navigate their owning frames. Blank iframe documents initialize during insertion, and iframe shadow roots use the correct document (#1357, #1359, #1385).
    • Startup and response handling avoid repeated work. Common browser methods are precompiled, bootstrap source is shared across isolates, inline styles are allocated on demand, and stored response bodies are encoded only when a client reads them.
    • Security dependencies are updated without replacing stealth profiles. Compression, Tokio, and TLS verification dependencies receive patched versions. Dependency audits pass without additional advisory exceptions (#1352, #1393).

    Rust API change

    StoredResponseBody.body changes from String to Vec<u8> and now contains the raw response bytes.

    Rust callers needing the previous text or base64 representation should use .cdp_body(). CDP response formats are unchanged (#1335).

    Security

    • Tokio is updated to 1.53.3, rustls-webpki to 0.103.16, and the affected WebPKI root packages to 1.0.10 (#1352).
    • Compression dependencies move to patched zstd 0.14.3, zstd-safe 8.2.0, and zstd-sys 2.1.2, with Brotli dependencies aligned. The existing emulation profiles are retained. Already-buffered certificate input avoids an unnecessary decoder allocation (#1393).
    • The network loader itself enforces the file:// opt-in, protecting callers that do not pass through navigation-level checks. Explicitly permitted file access remains available (#1280).
    • Scripted fetch() ignores forbidden request headers (#1358).
    • Wrapped JWK serialization and parsing use native JSON handling. Key wrapping validates permissions and extractability (#1290).

    CDP and automation

    • Runtime.evaluate executes directly in V8. Replacing the page’s eval no longer intercepts protocol evaluation, and global lexical bindings remain accessible to later evaluations (#1310).
    • Awaited evaluations no longer hold up all subsequent CDP commands. Function calls use native invocation and preserve JSON arguments.
    • DOM.getAttributes, DOM.requestNode, DOM.requestChildNodes, and DOM.getNodeForLocation support the top-level light DOM. Published node tracking belongs to each session and resets during navigation and teardown (#1300).
    • Initialization scripts are scoped to their target instead of leaking int
  2. v0.2.52026-10-17 · 2026-10-17 14:55 UTC
    查看更新说明

    v0.2.5

    This release focuses on MCP automation, storage correctness, and browser compatibility. It adds actionable snapshots, preserves tab runtimes, implements IndexedDB transactions, and fixes binary request interception. 128 commits since v0.2.4.

    Highlights

    • MCP snapshots include references agents can act on directly. Snapshots return accessible names, live control states, visible text, and bounded element descriptions. Stale references are rejected before an action reaches the wrong element (#1268, #1273).
    • MCP evaluations return completed results and real errors. Returned promises are awaited, scripts execute once, and exceptions, rejections, and timeouts produce tool errors. Active pages continue processing timers and network callbacks between requests (#1227, #1269, #1272).
    • Switching MCP tabs preserves page state. JavaScript globals, event listeners, frame state, and pending promises remain alive until the tab closes (#1274).
    • Storage retains data across more workflows. Web Storage survives navigation and target switching, MCP restores state into its declared origins, and IndexedDB transactions retain records and roll back aborted writes (#1252, #1259, #1270).
    • Iframe loading follows document lifecycle more closely. Fetched frames finish their author scripts before the element’s load event. iframe srcdoc loads in a native child document with inherited base URLs and sandbox restrictions (#1265, #1276).
    • Long-running servers gain worker recycling. Workers can be replaced by connection count, age, or Linux RSS limits. Replacements start before old workers drain their existing connections (#1211).

    Rust API change

    InterceptResolution::Continue.body is now Option<Vec> instead of Option. For owned text, replace Some(text) with Some(text.into_bytes()).

    None preserves the original request body. Some(Vec::new()) replaces it with an empty body. CDP clients continue sending base64-encoded postData; the wire format is unchanged (#874).

    Security

    • Rustls is updated from 0.23.45 to 0.23.46, addressing RUSTSEC-2026-0346 through RUSTSEC-2026-0349. The dependency audit passes without additional advisory exceptions (#1261).

    MCP and agent automation

    • browser_snapshot includes actionable references, accessible names, control states, and visible body text. Control descriptions and body text share the default 4,000-character content budget; URL and title are capped separately. Password values are omitted (#1273).
    • Snapshots and element listings use the existing native accessibility logic shared with CDP, including labels, ARIA naming, and visibility filtering (#1273).
    • Element references identify the original native node. Copying attributes to a replacement or clone no longer transfers a reference. New listings invalidate previous reference generations (#1268).
    • browser_evaluate accepts scripts, executes them once, and awaits returned promises. Synchronous execution, promise settlement, and result serialization are bounded. JavaScript failures return isError: true rather than successful null or error-string results (#1227, #1272).
    • browser_wait_for_function provides predicate-based waiting with timeout and polling options (#1227).
    • Active pages process timers, network callbacks, and pending navigation between requests over both stdio and HTTP. Idle pages park on browser wakeups (#1269).
    • Tab switching retains live runtimes instead of destroying and recreating page state. Closing a tab still releases its runtime and frames (#1274).
    • Storage-state import restores localStorage into each declared origin, including before navigation. Session storage remains scoped to the active tab. Export includes previously visited stored origins (#1270).
    • browser_fill_form stops before submission if a field fails and completes queued submit navigation before replying. Earlier field changes are not rolled back (#1272).
    • Render-enabled MCP accepts repeatable --font-dir options f
  3. v0.2.42026-10-04 · 2026-10-04 19:23 UTC
    查看更新说明

    v0.2.4

    This release focuses on automation reliability, browser compatibility, and reducing unnecessary engine work. It fixes frame teardown crashes, adds recovery for failed CDP workers, improves input and navigation behavior, and tightens the release validation gate. 212 commits since v0.2.3.

    Highlights

    • Frame teardown no longer crashes under garbage collection. Borrowed iframe contexts are cleaned up safely when their isolate is destroyed, fixing process aborts during page closure (#1197). • Failed CDP workers are reaped and restarted. Multiworker serving replaces exited workers and avoids routing new connections to workers known to be unavailable. Sessions on a failed worker still require reconnection (#1198). • Automation spends less time waiting unnecessarily. Idle polling, CDP reply stalls, redundant DOM traversal, and unchanged rendering work were reduced. Ready timers and awaited evaluations now yield so other browser work can proceed (#1078, #1111, #1131, #1132, #1137). • Input and page lifecycle behavior follow browsers more closely. Text insertion respects beforeinput and canceled keyboard events, readonly controls reject deletion, detached frames stop running timers, and document replacement preserves parser behavior (#1184, #1185, #1186, #1190, #1191). • Release CI requires all 33 obstacle-course stages. A failure shared by the baseline and candidate can no longer satisfy the absolute behavioral gate (#1195).

    Security

    • Teredo addresses containing a forbidden IPv4 destination are rejected by the SSRF gate (#1098). • The standalone renderer HTTP loader applies the same private-network restrictions as page networking (#1072). • Malformed request-interception URL rewrites fail closed instead of bypassing URL validation (#1076). • file:// navigation requires explicit permission at the browser layer, not just the CLI (#1070). • history.pushState() and replaceState() enforce same-origin URLs (#1057). • __Host- cookies require an explicit Path=/; cookie expiry parsing and IP-domain matching were hardened (#1099, #1073). • CSS-sized image and mask allocations, robots cache growth, and protocol-supplied values have tighter bounds (#1062, #1064, #1094).

    CDP and automation

    • Network events reach observer sessions, including intercepted requests, without losing their target routing (#1128, #1194). • Worker navigation responses expose the final HTTP status. Blocked requests no longer receive a fabricated status (#1119). • Redirected navigation responses are associated with the final document URL (#1139). • Active screencasts receive the new-document frame before load completion, reducing blank recordings of short navigations (#1134). • Runtime.getHeapUsage reports native V8 heap measurements, and HeapProfiler.collectGarbage performs collection (#1135). • Text insertion honors beforeinput cancellation, and canceled keydown events prevent default editing (#1184, #1185). • Readonly deletion and textarea newline behavior were corrected (#1186). • Readiness-file permission handling is portable to Windows (#1150).

    Networking and runtime

    • Fetch resolves when response headers are available rather than waiting for the complete buffered body (#1146). • Fetch and XHR cancellation stop native request work and release network-readiness counters. Canceled requests emit Network.loadingFailed (#1193). • Dynamic resources retain their request destinations (#1181). • Folded HTTP/1 response headers are accepted by both the normal and stealth transports (#1160). • Same-origin non-GET fetch and XHR requests send the appropriate Origin header (#1081, #1104). • WebAssembly streaming calls no longer reach an unset native callback that aborts the process (#1163). • Microtask exceptions are reported without discarding remaining queued work (#1174). • Initial iframe realms are created synchronously; deta

  4. v0.2.32026-09-20 · 2026-09-20 23:45 UTC
    查看更新说明

    v0.2.3

    Security, concurrency, and browser compatibility are the focus of this release. The JavaScript engine has been updated, exposed CDP and MCP servers now have built-in authentication, concurrent sessions are more reliable, and rendering gained another broad correctness and performance pass. 114 commits since v0.2.2.

    Highlights

    • CDP and MCP now protect exposed control ports. Non-loopback binds require a bearer token of at least 32 bytes. Browser-origin requests and invalid Host headers are rejected, request sizes and connection counts are bounded, and multiworker CDP preserves the same security checks (#1030, #1048). • Deno and V8 have been updated. deno_core moves from 0.350 to 0.412 and V8 to 150.4, removing the old advisory exceptions while retaining Obscura's runtime behavior and performance (#1031). • Concurrent automation is faster and more reliable. Concurrent pages keep independent live isolates, disconnected clients release their state, browser attachments receive unique sessions, and the multiworker server now starts immediately, uses OS-assigned ports, and avoids proxy latency (#872, #873, #978, #1048). • Rendering is faster and more faithful. Resource loads use the page transport, font databases and repeated text shaping are reused, live form state reaches paint, and layout, hit testing, scrolling, stacking, text decoration, and transform safety were improved (#879, #890, #935, #959, #983, #998, #1019). • Web platform behavior continues to close on browsers. CharacterData ranges, slots, URL setters, accessible names, Web IDL enumerability, selection-aware input, pointer events, :scope, and document.write ordering now follow browser behavior more closely (#887, #931, #984, #999, #1004, #1005, #1008, #1029).

    Security

    • CDP supports OBSCURA_CDP_TOKEN; MCP HTTP supports OBSCURA_MCP_TOKEN. Non-loopback binds are refused without a sufficiently long token. • CDP rejects browser-origin requests and DNS-rebinding-style Host headers. MCP browser requests are denied unless their origin is explicitly allowed. • MCP headers, bodies, batches, pending requests, and live connections are bounded. • CORS preflights validate methods and headers, intermediate redirect hops are checked, and credentials are removed on cross-origin redirects (#942, #967, #973). • Script-visible responses hide Set-Cookie, unexposed cross-origin headers, opaque response bodies, and internal resource bytes. • Cookie handling now preserves host-only scope, rejects public-suffix domains, enforces SameSite rules, and prevents insecure origins or document.cookie from overwriting protected cookies (#915). • WebCrypto output sizes are capped against memory exhaustion (#910). • Transform layers are dimension-capped so hostile transforms cannot force an aborting allocation (#1019). • Rustls was updated for RUSTSEC-2026-0285 (#1025). • Docker continues to run as the non-root uid 65532 (#859).

    CDP and automation

    • Concurrent pages on one connection retain independent JavaScript heaps and object handles (#872). • Browser attachment sessions are unique and detach independently (#978). • Client state is released after disconnect instead of accumulating in long-running servers (#873). • Discovery endpoints advertise the client-facing host and port (#945). • Fetch.fulfillRequest preserves binary bodies, multi-valued response headers remain intact, and the Fetch domain follows the same interception behavior as the main server path (#912, #913, #919). • History navigation updates its index only after successful navigation and retains network events (#920). • Runtime.evaluate and callFunctionOn preserve undefined in by-value results (#938). • DOM methods return protocol errors for unresolvable object ids instead of placeholder values (#917). • Backspace removes an entire surrogate pair, and Enter respects textarea selections (#939, #1005). • Mouse input now dispatches browser

  5. v0.2.22026-09-05 · 2026-09-05 07:35 UTC
    查看更新说明

    v0.2.2

    A wide compatibility and robustness pass: generated CDP clients (chromiumoxide, spider) now work end to end, Playwright form filling and context setup complete, one page script can no longer starve or crash the engine, and the stealth transport closes its remaining SSRF gaps. 126 commits since v0.2.1.

    Highlights

    • Generated CDP clients work. Page.enable emits the initial load sequence once per page with a schema-complete frame, so chromiumoxide's new_page no longer hangs before the first navigation and its strict Frame parser accepts every payload (#833, #703). • Playwright form filling and context setup complete. Input.insertText is implemented, Element.labels and HTMLLabelElement.control follow the HTML labelable-element rules so getByLabel resolves, Browser.grantPermissions is accepted during context init, and PerformanceObserver.supportedEntryTypes reports the types the engine emits (#577). • One bad script cannot starve a page. A throwing script or unhandled rejection is logged and the event loop keeps scheduling, matching Chrome; the bounded, quiescent, autonomous, and load-delaying pumps all continue (#699). • Worker processes survive deep and hostile pages. The style cascade runs on an explicit stack instead of recursing per DOM level, deeply nested timers are clamped, fetched_urls and fetch response bodies are capped, and watchdog threads are cancelled on drop (#705, #580). • Binary content survives end to end. XHR and fetch response bodies are read as bytes, Request headers and binary bodies are preserved, and Response.body/bodyUsed are exposed (#818).

    CDP and protocol

    • Runtime.evaluate and callFunctionOn report thrown or rejected values through exceptionDetails, the field clients rebuild page errors from (#746). • Execution contexts are owned by their session, and evaluation handles survive tab switches (#800). • Input.dispatchKeyEvent embeds key and code as JSON string literals, and objectId lookup sites embed the id the same way (#819). • DOM.getBoxModel returns Chrome-shaped integer coordinates, and a resolution failure is a real protocol error instead of a placeholder quad (#576). • The initial containing block is the viewport, so fixed and absolute boxes resolve bottom and percentage insets correctly (#675).

    Security

    • The SSRF deny-set covers IPv6 embedded-IPv4 (6to4/NAT64), CGNAT, and IANA special-purpose ranges, and the stealth client applies the same DNS guard and honors --allow-private-network (#810, #816). • Platform and user-agent overrides are embedded as JSON string literals, closing a script-injection path through profile setters (#792). • URL.createObjectURL rejects non-Blob input and mints Chrome-shaped blob URLs from a CSPRNG (#820). • PBKDF2 iteration and output caps landed earlier in this cycle (#580).

    Rendering

    • Text inputs paint their value, and textarea lays out as a real control box (#685). • The screenshot warmup fetches only the font sources the renderer will use, not every entry in a src list (#667), and the capture fallback reuses the retained resource cache. • SVG symbol glyph fallbacks render, and image paint is skipped only when the source truly failed (#698).

    DOM and JavaScript

    • postMessage honors targetOrigin, stopping cross-origin leaks between frames (#704). • HTMLInputElement.indeterminate is implemented, and label activation clicks the associated control (#732). • new FormData(form) reads the form and keys()/values() are iterable (#803). • Promise rejections dispatch unhandledrejection events, and the ICU default locale is pinned so Intl agrees with navigator.language (#734). • <body onload> content attributes fire, and inline text whitespace serializes correctly (#768, #785).

    MCP and packaging

    • MCP drains a synthesized navigation before the tool replies, so a click on a submit button issues its request before the answer returns (#618). • Binaries self-report their versio

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Obscura is a headless browser engine written in Rust, built for web scraping and AI agent automation. It runs real JavaScript via V8, supports the Chrome DevTools Protocol, and acts as a drop-in replacement for headless Chrome with Puppeteer and Playwright.

    Why Obscura over headless Chrome?

    MetricObscuraHeadless Chrome
    Memory30 MB200+ MB
    Binary size~70 MiB300+ MB
    Anti-detectBuilt-inNone
    Page load85 ms~500 ms
    StartupInstant~2s
    PuppeteerYesYes
    PlaywrightYesYes

    Obscura Cloud

    We are working on Obscura Cloud the hosted version, with managed infrastructure, residential proxies, and dedicated support. For people who want the engine without operating it themselves.

    The open-source engine stays Apache-2.0, fully featured. No feature gating, ever.

    Get on the waitlist → 📅 Book a demo →

    Open Source Support

    Sponsors

    Obscura is supported by organizations helping us build independent open-source browser infrastructure.

    Want to sponsor? Email [email protected].

    Install

    Download

    Grab the latest binary from Releases:

    # Linux x86_64
    curl -LO https://github.com/h4ckf0r0day/obscura/releases/latest/download/obscura-x86_64-linux.tar.gz
    tar xzf obscura-x86_64-linux.tar.gz
    ./obscura fetch https://example.com --eval "document.title"
    
    # Linux ARM64 (aarch64)
    curl -LO https://github.com/h4ckf0r0day/obscura/releases/latest/download/obscura-aarch64-linux.tar.gz
    tar xzf obscura-aarch64-linux.tar.gz
    
    # NixOS
    nix-env -iA nixpkgs.obscura
    
    # macOS Apple Silicon
    curl -LO https://github.com/h4ckf0r0day/obscura/releases/latest/download/obscura-aarch64-macos.tar.gz
    tar xzf obscura-aarch64-macos.tar.gz
    
    # macOS Intel
    curl -LO https://github.com/h4ckf0r0day/obscura/releases/latest/download/obscura-x86_64-macos.tar.gz
    tar xzf obscura-x86_64-macos.tar.gz
    

    Windows

    x86_64 Windows only (no 32-bit build).

    Download obscura-x86_64-windows.zip from Releases, extract it, and add the folder to your PATH. No C++ build tools are required.

    Alternatively, with Scoop installed:

    # Direct manifest install (available once this change merges to main):
    scoop install https://raw.githubusercontent.com/h4ckf0r0day/obscura/main/scoop/obscura.json
    

    No Chrome, no Node.js, no dependencies. Release archives include both obscura and obscura-worker: manual zip installs should keep both in the same directory for the parallel scrape command; scoop handles placement automatically (both binaries land in the app dir with shims on PATH).

    Archive suffixRenderingStealth transport
    noneYesNo
    -stealthYesYes
    -no-renderNoNo
    -no-render-stealthNoYes

    The scoop install pulls the default variant (rendering, no stealth). For stealth on Windows, download the -stealth archive manually.

    Linux release builds target Ubuntu 22.04 so the downloaded binary remains usable on common LTS servers with glibc 2.35+.

    Docker

    export OBSCURA_CDP_TOKEN="$(openssl rand -hex 32)"
    docker run -d --name obscura -p 127.0.0.1:9222:9222 \
      -e OBSCURA_CDP_TOKEN \
      h4ckf0r0day/obscura
    

    Image on Docker Hub. Multi-stage build on distroless/cc:nonroot — no shell, no package manager, runs as uid 65532, ~57 MB compressed. A mounted --storage-dir must be writable by uid 65532. Publish to host loopback as above; -p 9222:9222 exposes the port on every interface.

    Build from source

    git clone https://github.com/h4ckf0r0day/obscura.git
    cd obscura
    
    # Rendering
    cargo build --release -p obscura-cli --bins --features render
    
    # Rendering and stealth
    cargo build --release -p obscura-cli --bins --features render,stealth
    
    # No rendering
    cargo build --release -p obscura-cli --bins --no-default-features
    
    # No rendering, with stealth
    cargo build --release -p obscura-cli --bins --no-default-features --features stealth
    

    Requires current stable Rust (rustup.rs). First build takes ~5 min (V8 compiles from source, cached after). The stealth build also compiles BoringSSL and generates bindings, so it needs CMake, Clang, and the libclang/LLVM development libraries. On Ubuntu/Debian:

    sudo apt-get install build-essential cmake clang libclang-dev llvm-dev
    

    The rendering build uses rustls. The rendering-and-stealth build uses wreq/BoringSSL and therefore needs the additional build tools above.

    Quick Start

    Fetch a page

    # Get the page title
    obscura fetch https://example.com --eval "document.title"
    
    # Extract all links
    obscura fetch https://example.com --dump links
    
    # Render JavaScript and dump HTML
    obscura fetch https://news.ycombinator.com --dump html
    
    # Write dump or eval output to a file
    obscura fetch https://example.com --dump text --output page.txt
    
    # Stream the raw response body verbatim (binary-safe; bypasses the JS/DOM layer).
    # Use this for images, JSON, JS, CSS, or any non-HTML resource.
    obscura fetch https://picsum.photos/200/300 --dump original > photo.jpg
    
    # List every sub-resource URL the page would fetch (NDJSON; one record per asset)
    obscura fetch https://example.com --dump assets
    
    # Fetch through an HTTP or SOCKS proxy
    obscura --proxy socks5://127.0.0.1:1080 fetch https://example.com --dump text
    
    # Wait for dynamic content
    obscura fetch https://example.com --wait-until networkidle0
    
    # Bound navigation time for slow or broken pages
    obscura fetch https://example.com --timeout 10
    
    # Capture the settled page as PNG
    obscura fetch https://example.com --screenshot page.png
    
    # The screenshot flag also has a short form
    obscura fetch https://example.com -s page.png
    

    Testing against localhost / LAN dev servers

    Obscura blocks fetches to private/internal IPs by default (SSRF protection). To point it at a local dev server, pass --allow-private-network (or set OBSCURA_ALLOW_PRIVATE_NETWORK=1):

    obscura fetch http://127.0.0.1:3000 --allow-private-network --dump text
    
    # Works on any subcommand, e.g. the CDP server for local Puppeteer/Playwright:
    obscura serve --port 9222 --allow-private-network
    

    See docs/Environment-variables.md for the full allow/deny rules (DNS-resolution-time checks included).

    Rendering

    Official release archives and the Docker image include the rendering engine. It provides CSS layout and paint, viewport and full-page screenshots, scroll-aware fixed and sticky geometry, activity-driven CDP screencasting, and raster PDF export without starting Chromium.

    await page.setViewport({ width: 1440, height: 1000 });
    await page.goto('https://example.com', { waitUntil: 'load' });
    await page.screenshot({ path: 'page.png', fullPage: true });
    await page.pdf({ path: 'page.pdf', format: 'A4', printBackground: true });
    

    The current implementation covers block, inline, flex, grid, table, float, positioning, overflow, transform, text, image, SVG, canvas, background, border, and animation paths. It remains an evolving independent engine: long-tail CSS, some Web APIs, media playback, compositor effects, and platform font rasterization may differ from Chromium. The existing Puppeteer, Playwright, and MCP guides cover their capture APIs and limits.

    Start the CDP server

    obscura serve --port 9222
    
    # With stealth mode (anti-detection + tracker blocking)
    obscura serve --port 9222 --stealth
    

    Scrape in parallel

    obscura scrape url1 url2 url3 ... \
      --concurrency 25 \
      --eval "document.querySelector('h1').textContent" \
      --format json
    
    # Suppress scrape progress on stderr for script-friendly output
    obscura scrape https://example.com --quiet --format json
    
    # Scrape workers inherit the global proxy
    obscura --proxy http://127.0.0.1:8080 scrape https://example.com https://news.ycombinator.com
    

    Puppeteer / Playwright

    Media preference emulation covers color-scheme and reduced-motion overrides.

    Puppeteer

    npm install puppeteer-core
    
    import puppeteer from 'puppeteer-core';
    
    const browser = await puppeteer.connect({
      browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser',
    });
    
    const page = await browser.newPage();
    await page.goto('https://news.ycombinator.com');
    
    const stories = await page.evaluate(() =>
      Array.from(document.querySelectorAll('.titleline > a'))
        .map(a => ({ title: a.textContent, url: a.href }))
    );
    console.log(stories);
    
    await browser.disconnect();
    

    Playwright

    npm install playwright-core
    
    import { chromium } from 'playwright-core';
    
    const browser = await chromium.connectOverCDP({
      endpointURL: 'ws://127.0.0.1:9222',
    });
    
    const page = await browser.newContext().then(ctx => ctx.newPage());
    await page.goto('https://en.wikipedia.org/wiki/Web_scraping');
    console.log(await page.title());
    
    await browser.close();
    

    Form submission & login

    await page.goto('https://quotes.toscrape.com/login');
    await page.evaluate(() => {
      document.querySelector('#username').value = 'admin';
      document.querySelector('#password').value = 'admin';
      document.querySelector('form').submit();
    });
    // Obscura handles the POST, follows the 302 redirect, maintains cookies
    

    Benchmarks

    Page load:

    PageObscuraChrome
    Static HTML51 ms~500 ms
    JS + XHR + fetch84 ms~800 ms
    Dynamic scripts78 ms~700 ms

    The full benchmark suite (WPT conformance, obstacle course, real-world corpus, and vs-Chrome speed) lives in a separate repo: https://github.com/h4ckf0r0day/obscura-benchmark

    Stealth Mode

    Build with --features render,stealth, then enable stealth at runtime with the global --stealth flag. The stealth build includes the complete rendering engine; enabling stealth does not remove screenshot, screencast, PDF, CDP, or MCP functionality.

    Anti-fingerprinting

    • Per-session fingerprint randomization (GPU, screen, canvas, audio, battery)
    • Realistic navigator.userAgentData (Chrome 145, high-entropy values)
    • event.isTrusted = true for dispatched events
    • Hidden internal properties (Object.keys(window) safe)
    • Native function masking (Function.prototype.toString() → [native code])
    • navigator.webdriver = false

    Tracker Blocking

    • 3,520 domains blocked
    • Blocks analytics, ads, telemetry, and fingerprinting scripts
    • Prevents trackers from loading entirely
    • Enabled automatically with --stealth

    CDP API

    Obscura implements the Chrome DevTools Protocol for Puppeteer/Playwright compatibility.

    DomainMethods
    TargetcreateTarget, closeTarget, attachToTarget, createBrowserContext, disposeBrowserContext
    Pagenavigate, getFrameTree, lifecycleEvents, captureScreenshot, start/stopScreencast, printToPDF
    Runtimeevaluate, callFunctionOn, getProperties, addBinding
    DOMgetDocument, querySelector, querySelectorAll, getOuterHTML, resolveNode
    Networkenable, setCookies, getCookies, setExtraHTTPHeaders, setUserAgentOverride
    Fetchenable, continueRequest, fulfillRequest, failRequest (live interception), takeResponseBodyAsStream
    IOread, close (stream a large response body in chunks)
    StoragegetCookies, setCookies, deleteCookies
    InputdispatchMouseEvent, dispatchKeyEvent
    LPgetMarkdown (DOM-to-Markdown conversion)

    Fetch interception pauses matching main-document HTTP requests before transport. Fulfillment supplies the document body without contacting the server; continuation URL, method, body, and header overrides apply to that request only.

    To download a large resource without one giant Network.getResponseBody blob, call Fetch.takeResponseBodyAsStream then read it in chunks with IO.read / IO.close. Response bodies over the cache limit (OBSCURA_NETWORK_BODY_BUFFER_BYTES, default 2 MiB) are not retained, so raise that limit when you intend to stream large downloads.

    CLI Reference

    Tuning V8

    Obscura embeds V8 directly. Use --v8-flags to pass raw flags through to V8, same syntax as Chromium's --js-flags and Node's command-line flags. Most common use is raising the heap cap to fix JavaScript heap out of memory on JS-heavy pages:

    obscura --v8-flags "--max-old-space-size=4096" fetch https://example.com
    

    Heavy SPAs (script execution budget)

    Obscura caps the page's script-execution phase so one slow or hung page cannot stall a worker. The default budget is 30s; pages that finish sooner return immediately, so the cap only affects pages that keep running. A very heavy React/Vue/Angular SPA on a slow network can need more time to boot before it fires its data requests. Raise the budget with OBSCURA_SCRIPT_DEADLINE_MS (milliseconds), and pair it with a matching navigation timeout in your CDP client:

    OBSCURA_SCRIPT_DEADLINE_MS=60000 obscura serve --port 9222
    

    Modules that enhance an already-rendered page have a separate 3s per-module budget so one non-essential module cannot hold navigation open. Raise it for legitimate long-running modules such as a Vite HMR client:

    OBSCURA_MODULE_BUDGET_MS=10000 obscura serve --port 9222
    

    An unmounted SPA shell already gives its app modules the full OBSCURA_SCRIPT_DEADLINE_MS budget. OBSCURA_FETCH_TIMEOUT_MS controls the module's network request, not its evaluation time. See Environment variables for the complete timeout model.

    obscura serve

    Start a CDP WebSocket server.

    FlagDefaultDescription
    --port9222WebSocket port
    --proxy—HTTP/SOCKS5 proxy URL
    --stealthoffEnable anti-detection + tracker blocking
    --workers1Number of parallel worker processes
    --worker-max-connectionsoffRecycle a worker after N CDP connections (needs --workers > 1)
    --worker-max-rssoffRecycle a worker above this many MB resident (Linux)
    --worker-max-ageoffRecycle a worker after this many seconds
    --worker-drain-timeout120Seconds a replaced worker may finish its live connections before it is killed
    --font-dir—Recursively load fonts once per worker (repeatable; render build)
    --obey-robotsoffRespect robots.txt

    With multiple workers, exited children are reaped and restarted. New connections use ready workers only; if none are ready, the balancer returns HTTP 503. A crash still closes that worker's existing sessions, which clients must reconnect.

    The --worker-max-* options recycle workers gracefully: a replacement starts first, new connections go only to it, and the old worker keeps serving the connections it already has until they close (or the drain timeout passes), then exits. Each recycle is logged with its reason. Worker errors remain visible on stderr.

    obscura fetch <URL>

    Fetch and render a single page.

    FlagDefaultDescription
    --dumphtmlOutput: html, text, links, markdown, assets (NDJSON of every sub-resource URL the page references), or original (raw response body)
    --eval—JavaScript expression to evaluate
    --wait-untilloadWait: load, domcontentloaded, networkidle0. Network idle counts normal and stealth requests and requires a 500 ms quiet window within --timeout; an expired deadline is an error.
    --timeout30Maximum navigation time in seconds
    --waitadaptive, up to 5Post-load settling; an explicit value is a fixed delay in seconds
    --selector—Wait for CSS selector
    -s, --screenshot—Write a PNG screenshot (single URL; render-enabled build)
    --stealthoffAnti-detection mode
    --output—Write dump or eval output to a file
    --quietoffSuppress banner
    --proxy—Inherited global HTTP/SOCKS5 proxy URL

    obscura scrape <URL...>

    Scrape multiple URLs in parallel with worker processes.

    scrape and fetch --file ... --dump original keep at most --concurrency jobs active, starting the next URL as soon as a slot becomes available. Results remain in input order. URL input and completed results are still held in memory.

    FlagDefaultDescription
    --concurrency10Parallel workers
    --eval—JS expression per page
    --formatjsonOutput: json or text
    --quietoffSuppress scrape progress on stderr
    --proxy—Inherited global HTTP/SOCKS5 proxy URL for all workers

    MCP (Model Context Protocol)

    Obscura ships an MCP server that exposes browser automation tools to AI agents (Claude Desktop, Cursor, etc.).

    Start

    stdio (default) — for Claude Desktop and MCP clients that launch a subprocess:

    obscura mcp
    

    HTTP — for clients that connect over the network:

    obscura mcp --http --port 8080
    # endpoint: http://127.0.0.1:8080/mcp
    

    Optional flags (both transports):

    FlagDescription
    --proxy <URL>HTTP/SOCKS5 proxy
    --user-agent <UA>Custom User-Agent string
    --font-dir <DIR>Load additional fonts before rendering (repeatable; render build)
    --stealthEnable anti-detection mode

    Claude Desktop config

    {
      "mcpServers": {
        "obscura": {
          "command": "obscura",
          "args": ["mcp"]
        }
      }
    }
    

    Tools

    ToolDescription
    browser_navigateNavigate to a URL (url, optional waitUntil: load / domcontentloaded / networkidle0)
    browser_snapshotReturn the current page URL, title, readable body text, and element references
    browser_screenshotReturn the current page as an MCP PNG image (render-enabled build)
    browser_pdfReturn the current page as an embedded PDF resource (render-enabled build)
    browser_clickClick by current snapshot reference or CSS selector
    browser_fillSet an input value by reference or selector (triggers input + change)
    browser_typeAppend text to an input
    browser_press_keyDispatch a keyboard event (key, optional selector)
    browser_select_optionSelect an <option> by value or text
    browser_evaluateEvaluate a JavaScript expression and return the result
    browser_wait_forWait for a CSS selector to appear (selector, optional timeout in seconds)
    browser_network_requestsList network requests made by the current page
    browser_console_messagesReturn console messages logged by the page
    browser_closeClose the page and reset browser state

    The MCP server exposes still-image and PDF output. Use CDP when you need the streaming Page.startScreencast protocol.

    Integrations

    • Hermes agent plugin: run Hermes agent browser tasks on Obscura. The plugin spawns obscura serve per session (or connects to an already running server) and drives it over CDP, with optional --stealth.

    License

    Apache 2.0