# https://github.com/steel-dev/steel-browser Project Manual

Generated at: 2026-06-21 09:33:45 UTC

## Table of Contents

- [Overview & System Architecture](#page-1)
- [Sessions, Quick Actions & Browser Integrations](#page-2)
- [Plugin System, Chrome Extensions & Anti-Detection](#page-3)
- [Deployment, Configuration & Troubleshooting](#page-4)

<a id='page-1'></a>

## Overview & System Architecture

### Related Pages

Related topics: [Sessions, Quick Actions & Browser Integrations](#page-2), [Plugin System, Chrome Extensions & Anti-Detection](#page-3), [Deployment, Configuration & Troubleshooting](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)
- [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)
- [ui/package.json](https://github.com/steel-dev/steel-browser/blob/main/ui/package.json)
- [repl/package.json](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json)
- [api/extensions/recorder/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/extensions/recorder/package.json)
- [api/src/utils/schema.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/schema.ts)
- [api/src/utils/scrape/pdfToHtml.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/scrape/pdfToHtml.ts)
- [api/src/utils/casting.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/casting.ts)
- [api/src/utils/requests.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/requests.ts)
</details>

# Overview & System Architecture

## Purpose & Scope

Steel is an open-source browser API designed to give AI agents and applications programmatic control over real Chrome instances without the need to build browser-automation infrastructure from scratch. The project exposes a REST surface for session lifecycle, scraping, screenshots, PDF generation, and search, while delegating browser control to Puppeteer/Chrome DevTools Protocol (CDP) under the hood [Source: [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)].

The system is delivered as a self-hostable bundle (one combined Docker image) and is also offered as a managed "Steel Cloud" service. The same API is consumed by official Node and Python SDKs (`steel-sdk`), and clients can attach to running sessions with Puppeteer, Playwright, or Selenium because the underlying transport is standard CDP [Source: [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)].

The repository scope deliberately covers: full browser control, persistent session state, proxy chain management, custom extension loading, request/debug logging, anti-detection (stealth plugins and fingerprint management), automatic resource cleanup, and conversion utilities for markdown/readability/screenshot/PDF output [Source: [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)]. Community issue [#123](https://github.com/steel-dev/steel-browser/issues/123) confirms that users expect deeper documentation on these feature areas, so this overview surfaces the architectural shape that underpins them.

## System Components

The repository is organized as a monorepo with three independently versioned workspaces plus an extensions folder. Each package has its own `package.json` and runs as a distinct process in development, while the production Docker image bundles them.

| Component | Path | Runtime | Primary Responsibility |
| --- | --- | --- | --- |
| API server | [api/](https://github.com/steel-dev/steel-browser/blob/main/api/package.json) | Fastify 5 on Node ≥22 | REST API, session orchestration, CDP proxy, scrape/screenshot/PDF actions |
| Web UI | [ui/](https://github.com/steel-dev/steel-browser/blob/main/ui/package.json) | React 18 + Vite + Radix | Operator dashboard, session debugger, recording playback |
| REPL | [repl/](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json) | Node + tsx + puppeteer-core | Local scripting harness that connects to a live CDP endpoint |
| Recorder extension | [api/extensions/recorder/](https://github.com/steel-dev/steel-browser/blob/main/api/extensions/recorder/package.json) | Chromium extension (rrweb) | Captures DOM events inside the page for later replay |

The API package owns the long-lived browser processes; the UI is a stateless viewer and controller; the REPL is a developer convenience; and bundled extensions are loaded into the browser at launch. The `recorder` extension notably pulls `rrweb` and `@rrweb/packer` to serialise page activity, which the UI replays using `rrweb-player` [Source: [api/extensions/recorder/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/extensions/recorder/package.json)].

## Architecture & Data Flow

Steel follows a layered design: external clients hit the Fastify API, the API owns session managers that wrap Puppeteer pages, and Puppeteer talks to Chrome over a local CDP WebSocket. The WebSocket is also exposed outward so that SDKs and end-user scripts can attach Puppeteer/Playwright/Selenium to the same browser that backs the REST actions [Source: [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)].

```mermaid
flowchart LR
    Client[Client / SDK / REPL] -->|REST| API[Fastify API - api/]
    Client -.->|CDP WebSocket| Browser[Chrome Instance]
    API -->|Puppeteer-core 23.6.0| Browser
    API --> Schema[Zod Schemas - utils/schema.ts]
    API --> Scrape[Scrape utilities - scrape/pdfToHtml.ts, scrape/jsonToMarkdown.ts]
    API --> Cast[Page Casting - utils/casting.ts]
    API --> Filter[URL Pattern Filters - utils/requests.ts]
    UI[React UI - ui/] -->|REST + WS| API
    Browser --> Recorder[rrweb Recorder Extension]
    Recorder --> UI
```

Key cross-cutting concerns are implemented as shared utilities under [api/src/utils/](https://github.com/steel-dev/steel-browser/tree/main/api/src/utils):

- **Schema layer** uses Zod with a custom `buildJsonSchemas` helper to produce OpenAPI 3 documents that power both runtime validation and the bundled Swagger UI [Source: [api/src/utils/schema.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/schema.ts)].
- **Scrape layer** converts responses into structured formats. `pdfToHtml.ts` wraps MuPDF to render PDFs back into HTML with title, sections, and link extraction [Source: [api/src/utils/scrape/pdfToHtml.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/scrape/pdfToHtml.ts)]; `jsonToMarkdown.ts` pretty-prints JSON bodies inside fenced code blocks [Source: [api/src/utils/scrape/jsonToMarkdown.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/scrape/jsonToMarkdown.ts)].
- **Casting layer** applies remote navigation events (back/forward/refresh/goto) to a target Puppeteer page and fetches title and favicon metadata for the UI [Source: [api/src/utils/casting.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/casting.ts)].
- **Request filter layer** compiles user-supplied URL patterns into case-insensitive regular expressions and applies allow/deny host lists [Source: [api/src/utils/requests.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/requests.ts)].

The REPL package is intentionally minimal — it declares only `puppeteer-core` and `tsx` as runtime/dev dependencies and a single `start` script, confirming that it is meant to attach to an already-running API rather than spin up its own browser [Source: [repl/package.json](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json)].

## Technology Stack & Integration Patterns

The runtime stack is deliberately narrow. On the server side, Fastify 5 with plugins for CORS, multipart, sensible defaults, static, view, and Swagger powers the HTTP surface; `puppeteer-core@23.6.0` is the single browser-control dependency; and `proxy-chain`, `http-proxy`, `https-proxy-agent`, and `socks-proxy-agent` together implement the proxy-chain feature called out in the README [Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)]. Anti-detection is delivered through `fingerprint-generator@2.1.82` and `fingerprint-injector@2.1.82`, which are pinned to identical versions to keep the generator/injector pair in lockstep [Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)].

The UI package is a Vite-bundled SPA that consumes a generated OpenAPI client via `@hey-api/openapi-ts`, renders with Radix primitives and Tailwind, and uses `rrweb-player` to replay recordings produced by the recorder extension [Source: [ui/package.json](https://github.com/steel-dev/steel-browser/blob/main/ui/package.json)]. This codegen-driven approach means the UI never drifts from the API surface as long as `npm run generate-api` is re-run after schema changes.

Configuration is environment-driven (`dotenv`), and the production image exposes port 3000 for the API and 9223 for the CDP endpoint. The README documents one-liner Docker, Railway, and Render deploys, plus a local `docker run` command that maps both ports [Source: [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)].

## Known Limitations & Operational Considerations

Several architectural boundaries are visible directly from the source:

- **Single-node session affinity.** Each Chrome process is bound to the API node that launched it. Community issue [#144](https://github.com/steel-dev/steel-browser/issues/144) highlights the lack of built-in cluster support and session routing across nodes; the current architecture does not provide that out of the box.
- **Fingerprint generator brittleness.** Because the API pins `fingerprint-generator` and `fingerprint-injector` to the same minor version, a Chrome upgrade can break fingerprint consistency. Issues [#295](https://github.com/steel-dev/steel-browser/issues/295) and [#302](https://github.com/steel-dev/steel-browser/issues/302) both report "Failed to generate a consistent fingerprint after 10 attempts" with Chrome 146 at 1920x1080, forcing operators to set `SKIP_FINGERPRINT_INJECTION` as a workaround. Operators should treat the fingerprint versions as a coupled unit when planning Chrome upgrades [Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)].
- **Local-dev runtime requirements.** Issue [#230](https://github.com/steel-dev/steel-browser/issues/230) shows a `ReferenceError: File is not defined` when running `npm run dev` on Node versions missing the `File` global. Both `api` and `repl` workspaces declare `"node": ">=22.0.0"` in their engines, which is the supported floor for local development [Source: [repl/package.json](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json)].
- **Security posture is operator-managed.** The README describes Steel as a tool for building agents, and the project has an active coordinated vulnerability disclosure program (issue [#311](https://github.com/steel-dev/steel-browser/issues/311)). Self-hosters are responsible for network exposure, proxy trust, and CSP for any custom extensions they bundle.

## See Also

- [Sessions & Browser Lifecycle](#) — how session managers launch, persist, and release Chrome instances
- [Anti-Detection & Fingerprinting](#) — how `fingerprint-generator` and `fingerprint-injector` are wired into launches
- [Action Endpoints (Scrape / Screenshot / PDF)](#) — the `/v1/scrape`, `/v1/screenshot`, and `/v1/pdf` surface documented in the README
- [Deploying Steel](#) — Docker, Railway, and Render deployment paths referenced in the README

---

<a id='page-2'></a>

## Sessions, Quick Actions & Browser Integrations

### Related Pages

Related topics: [Overview & System Architecture](#page-1), [Plugin System, Chrome Extensions & Anti-Detection](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)
- [api/src/routes.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/routes.ts)
- [api/src/modules/sessions/sessions.controller.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/modules/sessions/sessions.controller.ts)
- [api/src/modules/sessions/sessions.routes.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/modules/sessions/sessions.routes.ts)
- [api/src/modules/sessions/sessions.schema.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/modules/sessions/sessions.schema.ts)
- [api/src/modules/actions/actions.controller.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/modules/actions/actions.controller.ts)
- [api/src/modules/actions/actions.routes.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/modules/actions/actions.routes.ts)
- [api/src/utils/scrape/readability.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/scrape/readability.ts)
- [api/src/utils/scrape/pdfToHtml.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/scrape/pdfToHtml.ts)
- [api/src/utils/casting.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/casting.ts)
- [api/src/utils/context.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/context.ts)
- [repl/README.md](https://github.com/steel-dev/steel-browser/blob/main/repl/README.md)
- [repl/package.json](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json)
- [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)
- [api/extensions/recorder/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/extensions/recorder/package.json)
</details>

# Sessions, Quick Actions & Browser Integrations

## Overview

Steel exposes three complementary ways to drive a Chromium instance: **Sessions** for stateful, long-lived browser workflows; **Quick Actions** (`/scrape`, `/screenshot`, `/pdf`) for one-shot read-only jobs; and **Browser Integrations** (Puppeteer, Playwright, Selenium, raw CDP) that let external automation frameworks attach to the same browser process. Together they cover the spectrum from low-level CDP control to high-level content extraction, all behind a Fastify HTTP API documented at `http://localhost:3000/documentation` Source: [README.md:1-120](https://github.com/steel-dev/steel-browser/blob/main/README.md).

The `api` package registers these capabilities as a Fastify plugin tree. The top-level `routes.ts` mounts the `sessions` and `actions` modules and the SDK exports in `api/package.json` expose the same building blocks as typed packages (`./plugin`, `./cdp-plugin`, `./logger`, `./storage`, `./browser`) Source: [api/package.json:1-60](https://github.com/steel-dev/steel-browser/blob/main/api/package.json). This layered design is what allows the official Node and Python SDKs and the bundled `repl` to all speak to a single browser instance.

```mermaid
flowchart LR
  Client["SDK / curl / Puppeteer / Playwright"] -->|HTTP| API["Fastify API<br/>(api/src/routes.ts)"]
  API --> Sessions["/v1/sessions<br/>sessions.controller.ts"]
  API --> Actions["/v1/{scrape,screenshot,pdf}<br/>actions.controller.ts"]
  API --> CDP["CDP endpoint (9223)"]
  Sessions --> Browser["Puppeteer-core<br/>Chrome Process"]
  Actions --> Browser
  CDP --> Browser
  Browser --> Repl["@steel-browser/repl<br/>(puppeteer-core)"]
```

## Sessions: Stateful Browser Lifecycle

### Purpose and Shape

The Sessions module is the centerpiece for stateful automation. It creates, releases, resets, and releases browser contexts, and persists cookies, localStorage, sessionStorage, and IndexedDB so workflows can be paused and resumed. The schema is defined in `sessions.schema.ts` and exposed as REST endpoints in `sessions.routes.ts` Source: [api/src/modules/sessions/sessions.routes.ts:1-80](https://github.com/steel-dev/steel-browser/blob/main/api/src/modules/sessions/sessions.routes.ts).

`SessionData` in the context utilities formalizes what is captured per origin:

| Storage Domain | CDP Domain Used | Notes |
| --- | --- | --- |
| `localStorage` | `DOMStorage.getDOMStorageItems` | Key/value per origin |
| `sessionStorage` | `DOMStorage.getDOMStorageItems` | Tab-scoped key/value |
| `indexedDB` | `IndexedDB` queries | Database + object store records |
| Cookies | `Network.getCookies` | Re-injected on relaunch |

Source: [api/src/utils/context.ts:1-60](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/context.ts).

### Lifecycle Hooks

Steel ships a plugin lifecycle that includes the most recent additions from v0.5.3-beta: `onSessionStart`, `onBeforeSessionEnd`, and `onAfterSessionEnd`. These hooks let plugin authors react when a session is created, about to close, or has just released its resources, complementing the earlier `onBrowserReady` hook (which now also supports promises) Source: [Release v0.5.3-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.3-beta), [Release v0.5.0-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.0-beta).

A typical session flow is:

1. `POST /v1/sessions` with options such as `proxyUrl`, `userDataDir`, `extensionIds`, `isSelenium`, or `fullscreen` Source: [README.md:90-130](https://github.com/steel-dev/steel-browser/blob/main/README.md).
2. The controller launches Puppeteer, applies the fingerprint and extension, and returns `{ id, wsUrl, debuggerUrl, viewerUrl }`.
3. Downstream calls (e.g. `POST /v1/sessions/:id/context` or `DELETE /v1/sessions/:id/release`) round-trip through the controller and invoke the relevant plugin hooks.
4. Resource cleanup is consolidated; the v0.4.5-beta release explicitly fixed default download behavior and error flows for session teardown Source: [Release v0.4.5-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.4.5-beta).

### SDK Usage

The Node and Python SDKs are thin wrappers around these routes. They expose a typed `client.sessions.create({ ... })` and `client.sessions.release(id)` API and allow swapping the cloud endpoint via the `baseURL` / `base_url` option for self-hosted deployments Source: [README.md:140-180](https://github.com/steel-dev/steel-browser/blob/main/README.md).

## Quick Actions: One-Shot Extraction

Quick Actions are read-only HTTP endpoints that boot a transient session, perform a single action, and return the result. They are documented as ideal for "simple, read-only, on-demand jobs" and are mounted under `/v1` Source: [README.md:150-200](https://github.com/steel-dev/steel-browser/blob/main/README.md).

The three actions currently supported:

| Endpoint | Body | Result |
| --- | --- | --- |
| `POST /v1/scrape` | `{ url, delay, ... }` | Cleaned HTML/markdown via `Defuddle` |
| `POST /v1/screenshot` | `{ url, fullPage, ... }` | PNG image of the page |
| `POST /v1/pdf` | `{ url, ... }` | Rendered PDF (parses back to HTML on retrieval) |

Source: [README.md:150-200](https://github.com/steel-dev/steel-browser/blob/main/README.md), [api/src/utils/scrape/readability.ts:1-30](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/scrape/readability.ts).

### Content Extraction Internals

`getDefuddleContent` runs `Defuddle` once on the raw HTML and inspects the result. If the result is "thin" (no markdown or fewer than 50 words and no extractor type), a second pass runs with aggressive stripping (`<script>`, `<style>`, `<noscript>` removed) and a relaxed selector set. The richer of the two passes wins, and its `content`/`contentMarkdown`/`wordCount` fields replace the originals Source: [api/src/utils/scrape/readability.ts:1-60](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/scrape/readability.ts).

PDFs follow a parallel path: the `pdfToHtml.ts` utility uses `mupdf` to walk sections, collect links, and wrap the result in an HTML envelope with metadata (`title`, `info:ModDate`). The `scrape` action can therefore return either HTML scraped from a live page or HTML reconstructed from a PDF Source: [api/src/utils/scrape/pdfToHtml.ts:1-40](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/scrape/pdfToHtml.ts), [api/package.json:30-80](https://github.com/steel-dev/steel-browser/blob/main/api/package.json).

## Browser Integrations: CDP, Puppeteer, Playwright, Selenium

### CDP and the bundled REPL

Steel exposes a WebSocket CDP endpoint (default port `9223`) that is compatible with any CDP client. The bundled `repl` package wires this endpoint to `puppeteer-core` so developers can drive a running Steel instance interactively:

```bash
cd repl
npm start   # runs src/script.ts with tsx
```

Source: [repl/README.md:1-15](https://github.com/steel-dev/steel-browser/blob/main/repl/README.md), [repl/package.json:1-20](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json). The `repl` intentionally depends on `puppeteer-core` (not full Puppeteer) because the browser is launched by the API process.

### Casting, Navigation, and Context Tools

`api/src/utils/casting.ts` provides low-level helpers (`navigatePage`, `getPageTitle`, `getPageFavicon`) used by casting/CDP plugins to map incoming events to `puppeteer-core` calls and to normalize URLs before `page.goto`. `api/src/utils/context.ts` complements these by extracting storage state from every visited origin so it can be restored on the next launch Source: [api/src/utils/casting.ts:1-50](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/casting.ts), [api/src/utils/context.ts:1-60](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/context.ts).

### First-Party Automation Frameworks

Puppeteer and Playwright can be pointed at Steel by using the session's `wsUrl` (or `debuggerUrl`) as the `browserWSEndpoint` / `connectOptions.wsEndpoint`. The README links to dedicated guides for Puppeteer, Playwright (Node and Python), and Selenium integrations Source: [README.md:160-200](https://github.com/steel-dev/steel-browser/blob/main/README.md). For Selenium, Steel offers a drop-in compatible mode activated with `isSelenium: true` when creating a session, exposing a WebDriver-compatible endpoint Source: [README.md:120-150](https://github.com/steel-dev/steel-browser/blob/main/README.md).

The recorder extension lives under `api/extensions/recorder/` and uses `rrweb` + `@rrweb/packer` to capture page activity for replay in the UI's `rrweb-player` Source: [api/extensions/recorder/package.json:1-15](https://github.com/steel-dev/steel-browser/blob/main/api/extensions/recorder/package.json), [ui/package.json:10-50](https://github.com/steel-dev/steel-browser/blob/main/ui/package.json).

## Common Failure Modes & Troubleshooting

Several community-reported issues map directly onto this surface area:

- **Fingerprint generation failures** — Self-hosted `steel-browser-api` v0.5.3 with `fingerprint-generator@2.1.82` can fail to generate a consistent fingerprint on Chrome 146 at fixed 1920x1080. The temporary workaround is to set `SKIP_FINGERPRINT_INJECTION=1`; the upstream issue is tracked in #302 and #295 Source: [issue #302](https://github.com/steel-dev/steel-browser/issues/302), [issue #295](https://github.com/steel-dev/steel-browser/issues/295).
- **`npm run dev` boot failure** — A `ReferenceError: File is not defined` inside `undici` when running the API locally; the typical resolution is to run on Node 22+ (which is also enforced via `engines` in `api/package.json`) Source: [issue #230](https://github.com/steel-dev/steel-browser/issues/230), [api/package.json:1-30](https://github.com/steel-dev/steel-browser/blob/main/api/package.json).
- **Feature gaps** — Community requests (#123, #144) highlight the desire for more documentation on advanced features and for cluster-level request routing to the node hosting a given session. The README confirms that all session-aware operations today are scoped to a single API instance Source: [issue #123](https://github.com/steel-dev/steel-browser/issues/123), [issue #144](https://github.com/steel-dev/steel-browser/issues/144).

When in doubt, the Swagger UI at `/documentation` and the Scalar reference at `/scalar` reflect the live schema defined by the modules cited above Source: [api/package.json:30-60](https://github.com/steel-dev/steel-browser/blob/main/api/package.json).

## See Also

- [Steel Browser README](https://github.com/steel-dev/steel-browser/blob/main/README.md)
- [Sessions Module](https://github.com/steel-dev/steel-browser/tree/main/api/src/modules/sessions)
- [Actions Module](https://github.com/steel-dev/steel-browser/tree/main/api/src/modules/actions)
- [Scraping Utilities](https://github.com/steel-dev/steel-browser/tree/main/api/src/utils/scrape)
- [Steel REPL](https://github.com/steel-dev/steel-browser/tree/main/repl)
- [Steel Recorder Extension](https://github.com/steel-dev/steel-browser/tree/main/api/extensions/recorder)
- Release notes: [v0.5.3-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.3-beta), [v0.5.2-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.2-beta), [v0.5.1-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.1-beta), [v0.5.0-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.0-beta), [v0.4.5-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.4.5-beta)

---

<a id='page-3'></a>

## Plugin System, Chrome Extensions & Anti-Detection

### Related Pages

Related topics: [Overview & System Architecture](#page-1), [Sessions, Quick Actions & Browser Integrations](#page-2), [Deployment, Configuration & Troubleshooting](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)
- [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)
- [api/extensions/recorder/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/extensions/recorder/package.json)
- [api/src/utils/browser.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/browser.ts)
- [api/src/steel-browser-plugin.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/steel-browser-plugin.ts)
- [api/src/services/cdp/plugins/core/base-plugin.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/services/cdp/plugins/core/base-plugin.ts)
- [api/src/services/cdp/plugins/core/plugin-manager.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/services/cdp/plugins/core/plugin-manager.ts)
- [api/src/services/cdp/plugins/core/index.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/services/cdp/plugins/core/index.ts)
- [api/src/plugins/browser-session.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/plugins/browser-session.ts)
- [api/src/plugins/browser.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/plugins/browser.ts)
- [repl/README.md](https://github.com/steel-dev/steel-browser/blob/main/repl/README.md)
- [repl/package.json](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json)
</details>

# Plugin System, Chrome Extensions & Anti-Detection

## Overview

Steel Browser is an open-source browser API that combines three extensibility surfaces into a single stack:

1. **A Node/TypeScript plugin system** that lets developers hook into the browser and CDP lifecycle.
2. **A Chrome extension loader** that can ship custom extensions (e.g., the bundled `steel-recording-extension`) into the launched Chrome instance.
3. **An anti-detection layer** built around `fingerprint-generator` and `fingerprint-injector`, paired with stealth helpers to reduce automated-traffic signals.

The README summarises this as "**Anti-Detection: Includes stealth plugins and fingerprint management**" and "**Extension Support: Load custom Chrome extensions for enhanced functionality**" ([README.md:55-63](https://github.com/steel-dev/steel-browser/blob/main/README.md)).

The API package exposes these surfaces as named subpath exports so they can be consumed independently of the running server ([api/package.json:9-35](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)):

| Subpath | Purpose |
| --- | --- |
| `@steel-browser/api/plugin` | Public plugin authoring entrypoint (`steel-browser-plugin.ts`) |
| `@steel-browser/api/cdp-plugin` | Base class for CDP-layer plugins (`services/cdp/plugins/core/base-plugin.ts`) |
| `@steel-browser/api/logger` | Browser-side logger plugin (`plugins/logging/browser-logger`) |
| `@steel-browser/api/storage` | Log storage interface used by instrumentation plugins |
| `@steel-browser/api/browser` | Browser type definitions |

## Plugin System Architecture

The plugin stack is split into two layers:

- **CDP plugins** — run inside the Node process, attached to Chrome DevTools Protocol sessions via `BaseCDPPlugin`. The class contract and manager orchestration live under `api/src/services/cdp/plugins/core/` ([api/package.json:13-19](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)).
- **Browser-context plugins** — ship JavaScript that runs inside the page via Puppeteer's `evaluateOnNewDocument`. The bundled `installMouseHelper` utility in `api/src/utils/browser.ts` is a representative example: it injects a cursor overlay guarded by a frame check so it only runs on top-level windows ([api/src/utils/browser.ts:38-89](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/browser.ts)).

A typical flow when a session starts looks like the diagram below.

```mermaid
flowchart LR
    Client -->|HTTP /v1/sessions| API[Fastify API]
    API --> PM[PluginManager]
    PM -->|attach| CDP[CDP Plugins<br/>BaseCDPPlugin]
    PM -->|launch hook| Browser[Browser Plugins<br/>browser-session.ts / browser.ts]
    Browser -->|evaluateOnNewDocument| Page[Target Page]
    PM -->|load flags| FG[fingerprint-generator<br/>fingerprint-injector]
    FG --> Page
```

### Plugin Lifecycle Hooks

The lifecycle is event-driven. Recent releases have stabilised the hook contract:

- **v0.5.3-beta** introduced `onSessionStart`, `onBeforeSessionEnd`, and `onAfterSessionEnd` as first-class plugin hooks ([Release v0.5.3-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.3-beta)).
- **v0.5.0-beta** allowed `onBrowserReady` to return a Promise, letting plugins await async setup before the browser is handed to the caller ([Release v0.5.0-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.0-beta)).
- **v0.5.2-beta** added the ability for plugins to override the disconnect handler ([Release v0.5.2-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.2-beta)).
- **v0.4.3-beta** improved plugin launch hook timing and added fingerprint override support ([Release v0.4.3-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.4.3-beta)).

Plugins authored against this contract implement `BaseCDPPlugin` from `services/cdp/plugins/core/base-plugin.ts` and are registered through `PluginManager` in `services/cdp/plugins/core/plugin-manager.ts` ([api/src/services/cdp/plugins/core/base-plugin.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/services/cdp/plugins/core/base-plugin.ts)).

## Chrome Extension Support

Steel can load arbitrary Chrome extensions into the launched Chrome instance, which is essential for capabilities like DOM recording and content scripts. The repository ships one reference extension:

- **`api/extensions/recorder/`** — `steel-recording-extension` uses `rrweb` and `@rrweb/packer` to capture DOM mutations for later replay ([api/extensions/recorder/package.json:1-18](https://github.com/steel-dev/steel-browser/blob/main/api/extensions/recorder/package.json)).

Extensions are bundled with Webpack and shipped at runtime alongside the API container. The REPL package (`@steel-browser/repl`) demonstrates the consumption side: it uses `puppeteer-core` to connect to the CDP WebSocket exposed by the API and drive the browser programmatically ([repl/README.md:1-15](https://github.com/steel-dev/steel-browser/blob/main/repl/README.md), [repl/package.json:1-23](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json)). The same REPL entrypoint can be reused by any extension-driven workflow that needs CDP-level access.

## Anti-Detection: Fingerprinting & Stealth

Anti-detection in Steel rests on two pinned dependencies:

| Package | Pinned version | Role |
| --- | --- | --- |
| `fingerprint-generator` | `2.1.82` | Generates consistent browser fingerprints (UA, screen, locale, hardware) |
| `fingerprint-injector` | `2.1.82` | Applies the generated fingerprint to every launched context |

Both are listed in `api/package.json` as runtime dependencies ([api/package.json:73-110](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)). The fingerprint pipeline is invoked from the launch path and is overridable by plugins — see the v0.4.3-beta fingerprint override support note in [Release v0.4.3-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.4.3-beta).

### Known Anti-Detection Failure Modes

The community has surfaced two related bugs that operators should be aware of when self-hosting:

- **Issue [#295](https://github.com/steel-dev/steel-browser/issues/295)** — `Failed to generate a consistent fingerprint after 10 attempts`. Reported against `steel-browser` and `steel-browser-api` images; the browser never launches and the container becomes unhealthy.
- **Issue [#302](https://github.com/steel-dev/steel-browser/issues/302)** — Same root cause but triggered specifically by Chrome 146 at a fixed `1920x1080` viewport, forcing operators to set `SKIP_FINGERPRINT_INJECTION` as a workaround.

The mitigation pattern documented in those threads is to disable fingerprint injection via the `SKIP_FINGERPRINT_INJECTION` environment variable when the pinned `fingerprint-generator@2.1.82` cannot produce a consistent profile for the requested viewport/UA combination.

> **Security note:** A coordinated vulnerability disclosure is currently open against the project ([Issue #311](https://github.com/steel-dev/steel-browser/issues/311)) via VulnCheck. Operators tracking anti-detection or extension behaviour should watch that issue for remediation guidance.

## Failure Modes & Operational Tips

| Symptom | Likely Cause | Mitigation |
| --- | --- | --- |
| `ReferenceError: File is not defined` on `npm run dev` ([Issue #230](https://github.com/steel-dev/steel-browser/issues/230)) | Node version older than what `undici` expects `File` to be globally defined | Use Node ≥ 22 (matches `engines` in [repl/package.json:18-22](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json)) |
| `Failed to generate a consistent fingerprint after 10 attempts` ([Issue #295](https://github.com/steel-dev/steel-browser/issues/295)) | `fingerprint-generator@2.1.82` cannot match the requested UA/viewport combination | Set `SKIP_FINGERPRINT_INJECTION=1` or adjust the requested viewport ([Issue #302](https://github.com/steel-dev/steel-browser/issues/302)) |
| Extension not visible in launched Chrome | Extension not built or not copied into the container image | Run `npm run build` inside `api/extensions/recorder/` ([api/extensions/recorder/package.json:10-12](https://github.com/steel-dev/steel-browser/blob/main/api/extensions/recorder/package.json)) |
| Custom Chrome binary ignored | `CHROME_EXECUTABLE_PATH` set to a path that does not exist | `getChromeExecutablePath()` only warns and falls back to `/usr/bin/chromium` ([api/src/utils/browser.ts:3-22](https://github.com/steel-dev/steel-browser/blob/main/api/src/utils/browser.ts)) |

## See Also

- [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md) — high-level feature overview and deployment options.
- [Release v0.5.3-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.5.3-beta) — most recent plugin hook additions.
- [Release v0.4.3-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.4.3-beta) — plugin launch hook timing and fingerprint override support.
- [Issue #144](https://github.com/steel-dev/steel-browser/issues/144) — community request for cluster/session-affinity routing, relevant when scaling plugin state across nodes.

---

<a id='page-4'></a>

## Deployment, Configuration & Troubleshooting

### Related Pages

Related topics: [Overview & System Architecture](#page-1), [Sessions, Quick Actions & Browser Integrations](#page-2), [Plugin System, Chrome Extensions & Anti-Detection](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)
- [Dockerfile](https://github.com/steel-dev/steel-browser/blob/main/Dockerfile)
- [api/Dockerfile](https://github.com/steel-dev/steel-browser/blob/main/api/Dockerfile)
- [api/entrypoint.sh](https://github.com/steel-dev/steel-browser/blob/main/api/entrypoint.sh)
- [.env.example](https://github.com/steel-dev/steel-browser/blob/main/.env.example)
- [api/.env.example](https://github.com/steel-dev/steel-browser/blob/main/api/.env.example)
- [api/src/config.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/config.ts)
- [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)
- [ui/package.json](https://github.com/steel-dev/steel-browser/blob/main/ui/package.json)
- [repl/package.json](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json)
- [repl/README.md](https://github.com/steel-dev/steel-browser/blob/main/repl/README.md)
</details>

# Deployment, Configuration & Troubleshooting

This page covers the operational side of the **steel-browser** project: how to run the API, the UI, and the REPL; how to tune the platform through environment variables; and how to recover from the most common failure modes reported by the community.

## Overview

steel-browser is shipped as a multi-package workspace. Three services make up a working installation:

- **API** – a Fastify-based Node service that manages Chrome sessions, proxies CDP traffic, and exposes the REST + WebSocket surface used by the Python and Node SDKs. Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)
- **UI** – a Vite + React debugging console that lists live sessions, replays recorded sessions via `rrweb-player`, and provides a Swagger / Scalar reference browser. Source: [ui/package.json](https://github.com/steel-dev/steel-browser/blob/main/ui/package.json)
- **REPL** – a small Puppeteer-based script that connects over CDP for ad‑hoc scripting. Source: [repl/package.json](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json)

The default public ports are `3000` for the API and `9223` for the CDP endpoint, with the UI dev server bound to `5173` by default. Source: [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)

```mermaid
flowchart LR
  Client[SDK / curl / Puppeteer] -->|HTTPS :3000| API[Fastify API]
  Client -->|WSS :9223| CDP[Chrome DevTools Protocol]
  API --> Browser[Headless Chrome]
  UI[Vite UI :5173] -->|OpenAPI proxy| API
  REPL[repl/src/script.ts] -->|WS :9223| CDP
```

## Deployment Options

### Quick Deploy (managed / one-click)

The README documents three hosted paths so a working instance can be brought up without a local toolchain:

| Method | Surface | Source |
| --- | --- | --- |
| GHCR combined image (`ghcr.io/steel-dev/steel-browser`) | API + UI in one container | [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md) |
| Railway one-click | API + UI on Railway | [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md) |
| Render one-click | API + UI on Render | [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md) |

For Steel Cloud, the same `steel-sdk` is reused against a managed endpoint by changing `baseURL` (Node) or `base_url` (Python). Source: [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)

### Docker (self-hosted)

A minimal self-hosted run uses the prebuilt image and exposes both ports:

```bash
docker run -p 3000:3000 -p 9223:9223 ghcr.io/steel-dev/steel-browser
```

The image's entrypoint, [api/entrypoint.sh](https://github.com/steel-dev/steel-browser/blob/main/api/entrypoint.sh), bootstraps the API process and prepares the recorder extension (which is built from [api/extensions/recorder/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/extensions/recorder/package.json) via `npm run prepare:recorder`). Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)

### Local development

Each package has its own scripts. The API supports hot reload through `tsx watch`, the UI through Vite, and the REPL through `tsx`:

```bash
# API
cd api && npm run dev          # tsx watch src/index.ts
# UI
cd ui  && npm run dev          # vite --host 0.0.0.0
# REPL
cd repl && npm start           # tsx ./src/script.ts
```

Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json), [ui/package.json](https://github.com/steel-dev/steel-browser/blob/main/ui/package.json), [repl/README.md](https://github.com/steel-dev/steel-browser/blob/main/repl/README.md)

## Configuration

Configuration is exclusively environment-driven, parsed in [api/src/config.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/config.ts) using `dotenv`. The full schema lives in [api/.env.example](https://github.com/steel-dev/steel-browser/blob/main/api/.env.example) and the root [`.env.example`](https://github.com/steel-dev/steel-browser/blob/main/.env.example).

| Variable (selected) | Default | Purpose |
| --- | --- | --- |
| `PORT` | `3000` | API HTTP port. Source: [api/src/config.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/config.ts) |
| `CDP_PORT` | `9223` | Chrome DevTools Protocol WebSocket port. Source: [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md) |
| `STEEL_API_KEY` | _empty_ | Bearer token for incoming REST/WS requests. Source: [api/.env.example](https://github.com/steel-dev/steel-browser/blob/main/api/.env.example) |
| `PROXY_URL` | _empty_ | Upstream proxy chained into the browser via `proxy-chain`. Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json) |
| `SKIP_FINGERPRINT_INJECTION` | `false` | Disables stealth/fingerprinting at launch. Source: [api/.env.example](https://github.com/steel-dev/steel-browser/blob/main/api/.env.example) |
| `OPTIMIZE_BANDWIDTH` | `false` | Reduces traffic between API and browser (added in v0.4.2). Source: [GitHub release v0.4.2-beta](https://github.com/steel-dev/steel-browser/releases/tag/v0.4.2-beta) |
| `SCRAPE_EVAL_HEAVY` | _unset_ | Enables heavier evaluation test path. Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json) |

Logging uses `pino` and `pino-pretty` and is configured through the same file. Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)

## Troubleshooting

The following failure modes appear repeatedly in the issue tracker and are directly addressable from configuration.

### Browser fails to launch with fingerprint errors

Self-hosted containers on Chrome 146 at a fixed `1920x1080` viewport throw `Fingerprint error during generation: Failed to generate a consistent fingerprint after 10 attempts` and the container becomes unhealthy. The root cause is a mismatch between the pinned `fingerprint-generator@2.1.82` and the new Chrome version. Two workarounds are used in the wild:

- Set `SKIP_FINGERPRINT_INJECTION=true` to bypass stealth and restore startup. Source: [Issue #302](https://github.com/steel-dev/steel-browser/issues/302), [Issue #295](https://github.com/steel-dev/steel-browser/issues/295)
- Upgrade the `fingerprint-generator` / `fingerprint-injector` pair to a version that supports the new UA string, then rebuild the image.

### `ReferenceError: File is not defined` during `npm run dev`

`undici` (a transitive dependency) references the `File` Web API, which is missing in Node < 18. Both [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json) and [repl/package.json](https://github.com/steel-dev/steel-browser/blob/main/repl/package.json) declare `"engines": { "node": ">=22.0.0" }` (the API itself requires modern Node for the dev experience). Source: [Issue #230](https://github.com/steel-dev/steel-browser/issues/230). Use Node 20+ LTS (22+ recommended) and a clean `node_modules` install.

### Recorder extension missing

The session recorder relies on a webpack build of the rrweb extension that is **not** produced during `npm install`. It is built on demand by `npm run prepare:recorder` (called automatically by `npm run dev`). Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json). In Docker, [api/entrypoint.sh](https://github.com/steel-dev/steel-browser/blob/main/api/entrypoint.sh) performs the same step during image build; a missing recorder usually means the build step was skipped or its output pruned.

### Session affinity and clustering

There is no built-in cross-node session routing: a session ID is valid only on the node that owns the Chrome process behind `CDP_PORT`. Multi-node deployments must front the cluster with a sticky router keyed on the session id returned by `/v1/sessions`. Source: [Issue #144](https://github.com/steel-dev/steel-browser/issues/144).

### Security disclosures

The project runs a coordinated vulnerability disclosure program. Report suspected issues privately before opening a public ticket. Source: [Issue #311](https://github.com/steel-dev/steel-browser/issues/311).

### Diagnostics checklist

1. `GET /health` and the Swagger UI at `/documentation` to confirm the API is up. Source: [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md)
2. Inspect `pino` logs (or `docker logs <container>`) for fingerprint, proxy, and CDP errors.
3. From the REPL, connect directly to `ws://host:9223` to isolate API problems from browser problems. Source: [repl/README.md](https://github.com/steel-dev/steel-browser/blob/main/repl/README.md)
4. Re-run `npm test` (Vitest) inside `api/` to catch regressions introduced by configuration changes. Source: [api/package.json](https://github.com/steel-dev/steel-browser/blob/main/api/package.json)

## See Also

- [README.md](https://github.com/steel-dev/steel-browser/blob/main/README.md) – high-level features, deploy buttons, and SDK pointers
- [api/src/config.ts](https://github.com/steel-dev/steel-browser/blob/main/api/src/config.ts) – authoritative list of environment variables
- [api/.env.example](https://github.com/steel-dev/steel-browser/blob/main/api/.env.example) – documented defaults
- [repl/README.md](https://github.com/steel-dev/steel-browser/blob/main/repl/README.md) – CDP debugging scripts
- [GitHub releases](https://github.com/steel-dev/steel-browser/releases) – changelog for flags such as `OPTIMIZE_BANDWIDTH` and fullscreen

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: steel-dev/steel-browser

Summary: Found 11 structured pitfall item(s), including 3 high/blocking item(s). Top priority: Installation risk - Installation risk requires verification.

## 1. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/steel-dev/steel-browser/issues/302

## 2. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/steel-dev/steel-browser/issues/295

## 3. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/steel-dev/steel-browser/issues/230

## 4. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: runtime_trace
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Repro command: `docker run -p 3000:3000 -p 9223:9223 ghcr.io/steel-dev/steel-browser`
- Evidence: identity.distribution | https://github.com/steel-dev/steel-browser

## 5. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/steel-dev/steel-browser

## 6. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/steel-dev/steel-browser

## 7. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/steel-dev/steel-browser

## 8. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/steel-dev/steel-browser

## 9. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/steel-dev/steel-browser/issues/311

## 10. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/steel-dev/steel-browser

## 11. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/steel-dev/steel-browser

<!-- canonical_name: steel-dev/steel-browser; human_manual_source: deepwiki_human_wiki -->
