Skip to content
On this page

Security ​

What the bridge promises about your browser, what gates each promise, and where the promise stops. The mechanism detail and every accepted residual are the trust boundaries ledger; what each tool can reach is the tool risk matrix.

The bar, in one line ​

A program you installed cannot use your browser without you noticing.

What the bar is not: "nothing can ever touch your browser". You installed this bridge so an agent can drive your browser. The bar is the standard the project holds itself to, not a claim that it is met everywhere today.

One place falls short, on purpose. On an approved origin, reads including masked cookies and storage run without a per-action prompt, and tab titles and URLs are readable with no approval at all. Where we deliberately stop names the gaps a reader meets first; the ledger carries every residual.

What is at stake, and who is trusted ​

The assets:

  • Your authenticated browser sessions: cookies including httpOnly, and web storage tokens. Whoever holds them acts as you on the sites you are logged into, past the password and the second factor you already entered.
  • Page content you can see.
  • The ability to act as you: click, fill, navigate, run code.
  • The wire protocols: a corrupted stream can hang or crash the bridge.
ActorTrusted?Notes
Youyesown the machine and the browser profile
The MCP client (Claude Code, Codex, ...)yes, by design, once pairedyou configured it; it drives the tools
The Rust binary (MCP server and native host)yesthe thing being secured
The MV3 extensionyesruns alongside untrusted page code
The web pagenomay be attacker-controlled; may carry prompt injection
Other local users and processesnomay try to reach the bridge socket
The networkout of scopeno remote surface; everything is localhost or stdio

Three assumptions the model rests on:

  • A single-user machine. No hostile local user shares your UID. Other users and other same-user binaries are rejected; a same-user attacker running this very binary is not (the non-goals).
  • The MCP client is trusted. A malicious client you installed yourself already has whatever you granted it. The tools exist to be driven by that client.
  • Chrome's sandbox and extension model hold. The bridge relies on MV3 isolation between content scripts and page JS, and on Chrome enforcing host permissions.

Do not become the cheapest door ​

Stealing cookies directly already costs an attacker something on every OS. A bridge that any local program can reach for free costs less than that, and an attacker takes the cheapest door.

PlatformReading the cookie store directlyA low-bar localhost bridge
macOSthe cookie key sits in the login Keychain; another app reading it raises a Keychain prompt unless the user already allowed that appsilent
Windowsapp-bound encryption: a SYSTEM-privileged service releases the key to the browser alone, so a same-user process needs SYSTEM or code injection into the browsersilent
Linuxthe key sits in the session's secret store, or a fixed fallback; a same-user process in an unlocked session reads it with no promptsilent

The left column describes the browser vendors' published protections. It is a claim about other software, not something this project tests.

The conclusion: a silent bridge lowers the bar the machine already had. Where that bar is already low, as on Linux, the bridge must at least not add a second free door.

Who attacks, and what answers each ​

Ranked by this project's judgment of real-world frequency, not a measurement: a page the agent is reading comes first, software you installed that is not the paired harness second, and persistent malware already running as you third. The third is out of scope by design; the bridge only refuses to make its job easier.

ThreatWhat answers itDetail
A page influences the agent into acting on it without approvalpage-level tools run only on an origin you approved; a new origin prompts you and requests the host permission; the page cannot add itselfboundary 4
Prompt injection: page content tricks the model into a dangerous tool callpage content is data to the agent, not commands; the dangerous actions confirm on a window the page cannot reach, and page_eval confirms every call showing the full codewhat you confirm
Credential or token exfiltrationcookies and storage are read-only, allowlist-scoped, and masked before they leave the extension; page_text masks passwords and card-like numberstool risk matrix
Another local process hijacks the bridgeevery connection passes a same-user check, mutual executable attestation (the platform table owns what each OS measures), and an HMAC challenge-response whose per-run secret never crosses the wireboundary 2
A malformed or oversized message crashes or corrupts the bridgelength-checked framing, a panic hook that keeps panics off the protocol stream, parse errors surfaced rather than fatal, fuzzed parsersboundary 3
Silent pairing: a process able to write an MCP client config stands up the whole chain unnoticedtwo ceremonies you run: pair mints the host key behind a confirmation typed on a terminal, and the browser enrolls a WebAuthn credential; every later grant needs a presence proofboundary 3
A revoked client or host keeps acting because revocation does not reach the enforcement pointone trust record, re-read with its epoch at every enforcement point. A revoked client's relay is dropped and its next request and re-attach refused; a revoked host key is pushed to the extension, which marks its pin compromised and closes its own request gate until the user re-pairsboundary 1

The four hops, and what gates each ​

text
MCP client --(1)-> Rust MCP server --(2)-> native host --(3)-> extension --(4)-> web page
   (trusted)         (trusted)           (trusted)         (trusted)        (UNTRUSTED)
HopWho may cross itWhat gates it
1. MCP client to MCP server, over stdiothe harness that spawned the server, once pairedthe server attests the spawning process and checks it against the paired clients; unmatched, unmeasurable, or an unreadable record all fail closed
2. MCP server to native host, over the bridge socketthis binary, run by this user, holding the run secretno listening port; a kernel peer-UID check; mutual executable attestation; an HMAC challenge-response; a role-declaring attach frame
3. Browser to native host, over native messagingour extension alone, by the manifest; the host the user paired, by its pinned keythe manifest pins the extension id; the extension pins the host's P-256 key; capability-granting acts need a presence proof the host verifies: a WebAuthn assertion, or the window where the rule admits no credential
4. Extension to web pagenothing the page asks forthe origin allowlist; confirmations on a window off the page's DOM; masking at egress; trust state in storage the page cannot read

One record ties the hops together: trust.json in the runtime directory holds the paired clients, the kill latch, and the browsers' enrolled credentials, and every enforcement point reads it fail-closed. The trust record in the ledger owns it.

What you confirm, and what counts as presence ​

The confirmation window. Submit and link clicks, page_press, page_select, page_eval, tab_close, and page_upload confirm on an extension-owned window in its own process, which the page cannot read, focus, overlay, auto-click, or auto-dismiss. A timeout, a closed window, or a missing provider all deny.

  • Every page_eval and page_upload call reconfirms. The fail-safe defaults own what each confirmation shows and how long the grace window lasts.
  • One approved click covers repeats within the window's per-tab key. page_eval is never in it.
  • Low-risk tools run unprompted on an approved origin: navigation, page_text, tab_list, masked cookie and storage reads.

Presence. Removing capability is friction-free: kill, revoke, and uninstall need no proof, because fail-closed is the safe state. Granting or restoring capability demands one proof of a human present, consumed by exactly one act:

SurfaceThe proofWhat it stops
the extension, on a browser with an enrolled credentiala WebAuthn tap on the browser's authenticator (a platform biometric, a PIN, a security key), verified by the host against the credential enrolled under this browserany answer not signed by that enrolled credential; what the gesture itself proves is the authenticator's business, the ledger's hardware residual
the extension, where the rule admits no credential: the browser has none for its own acts, the machine has none for enrollinga click on the confirmation window, labelled a software confirmationsilent and scripted acts, not a hostile same-user process
the CLI (pair, pair-client, unkill, policy set)a phrase typed on a real terminal; a piped stdin is refused before any promptscripted, piped, and accidental grants, not a same-user process that allocates a pty

An enrolled browser is never demoted to the window. The acts behind the gate, and who answers each:

  • releasing the kill switch: the browser's own credential;
  • enrolling another browser: any enrolled credential;
  • page_eval and page_upload where the policy's presenceConfirm is on: the browser's own credential, the request naming the op and the page's origin;
  • minting the host key: the CLI's terminal;
  • pairing a client, and relaxing the policy (a policy set, a rollback that relaxes, or the first baseline): the browser's own credential from the options page, the window where that browser enrolled none, or the CLI's terminal. policy set takes that proof whatever direction its edits run. A tightening is free only on the restrict lane below.

Two policy lanes. The grant lane (policy set, a relaxing rollback, a loosening on the options page) signs with the host key behind the presence proof above. The restrict lane (policy restrict, a tightening rollback, a tightening on the options page) travels unsigned and free, because a forged restriction can only remove capability. The CLI page owns the commands; the defaults table owns what relaxing each gate costs you.

Where we deliberately stop ​

Each gap below is accepted on purpose. The ledger entry it links to states what bounds it.

  • No per-read prompts on approved sites. A prompt on nearly every step teaches you to click through the prompts that guard the dangerous actions. Owner: read exfiltration.
  • A click grace window on approved sites. The cost of the window above: unrelated same-origin code can ride one approval for its duration. Owner: the defaults table.
  • Same-user re-execution of our own binary is accepted. Attestation rejects a different program, not the genuine binary started by a same-user attacker. Owner: boundary 2.
  • A compromised paired harness keeps its admitted identity. Attestation identifies a binary, not an intention, so a paired client that turns hostile stays trusted until revoked. Owner: boundary 1.
  • The software floors are labelled, not hidden. The window for an unenrolled browser and the terminal for the CLI attest intent on a trusted surface, not hardware, and every audited grant names the path that authorized it, pair's included. Owner: boundary 3.
  • A credential deleted from the authenticator stays enrolled. The host never learns that an authenticator or an OS passkey store dropped a credential, so that browser keeps refusing the window and loses its presence-gated acts until revoke <browser> forgets the enrollment. Owner: boundary 3.

Where the bar holds today, per OS ​

On every OS, harness admission is enforced only once one client is paired; before that the server serves whatever spawned it and logs that posture at ERROR level on every start.

OSClient attestationUser presencePolicy grant laneWhere the bar holds
macOSthe spawning harness's code identity, checked against the paired clientsthe browser's authenticator on an enrolled browser; the window otherwise; the terminal for the CLIpair mints the host key into the Keychain, so a grant from either surface signs herethe bridge admits only this binary, run by this user, holding the run secret
Linuxthe spawning harness's image hash, checked against the paired clientsthe same ladderpair mints the key into the Secret Service, or a 0600 file with --file-storeholds on the same gates
Windowsthe spawning harness's image hash and Authenticode publisher, checked against the paired clients; pairing is the CLI page'sthe same ladderpair mints the key into the Credential Managera named pipe only this user can open, mutual image attestation, the run secret

The grant lane needs a host key, and pair mints one on every OS, so a signed policy baseline is available on all three. The Windows residuals behind that row are in boundary 2; the platform table in the security policy owns the per-mechanism state.

Against the convenience-first design class ​

A common design for browser automation puts convenience first. The left column describes that design class by the choices that define it; what the browser does in response is a claim about other software, and the column names no product.

PointConvenience-first design classThis project
Who can reach the bridgea localhost port open to any local processonly this binary, run by this user, holding the run secret
The debug port and its bannera debug port open to any local process at all times; the browser's debugging banner shows only while a client is attachedno debug port; the banner shows only while a debugger-backed tool or the opt-in CDP mode holds an attach (the matrix marks which tools)
Default accessfull access to every site from the first callpage actions and reads run only on a site you approved, except tab titles and URLs; the riskiest tools are off by default under host-owned policy
Per-action confirmationnonethe confirmation-gated actions confirm on a window the page cannot reach; page_eval and page_upload reconfirm every call; relaxing a gate is a signed, presence-gated policy change
Local malwaredeclared out of scope, and nothing is done about itout of scope too, but the bridge refuses to be the cheapest door: a different same-user program is rejected at the socket, and once one client is paired every harness must match the allowlist

Explicit non-goals ​

  • A compromised OS account, or a same-user attacker who runs this very binary: byte-identical to the legitimate host, so neither a hash nor a code signature can tell them apart.
  • A malicious MCP client you configured yourself.
  • Multi-user or shared-machine isolation.
  • Remote attackers: there is no remote attack surface.

Changing something, or reporting something ​

A change that moves any of this is security-relevant and goes through the review bar, which names the row of this page it moves. A suspected breach goes to the reporting channel, never a public issue.

Built from main at 25ae5f4Source: docs/security.md