Security

Security is the product.

PairBrowse drives a browser that is signed in to your accounts. So it is built to keep that browser to you and your agent, and to limit what a malicious web page can talk the agent into.

Card number•••• ••••
Place orderClaude

PairBrowse asks

Pay: Place order

A card field is in this form, so this click is a payment.

DenyAllow
Ordinary steps run on their own. Real commitments wait for you.

What PairBrowse guarantees

Enforced by a local helper, not by asking the model nicely.

A local helper on a private socket

There is no remote-debugging port. The browser is driven over a private pipe by the PairBrowse helper, and the only way in is a socket file that only your user account can open. Everything in ~/.pairbrowse is created private to you.

Guarded clicks

The helper reads every click by what it does, from the page's structure: card and IBAN fields, payment frames, danger-styled buttons, confirmation dialogs. Pay, delete, publish, send and final submit clicks ask you. Nothing about a click leaves your computer.

Secrets by name, masked output

Claude types a secret's name. The helper fills the real value only on the HTTPS domains you listed, checked against the tab's real address, and masks it in everything Claude reads back. No screenshot is sent while a saved password shows on the page.

A key-protected live view

The live view listens on 127.0.0.1 only, on a random port, behind a random 256-bit key. Other host names are refused, it can't be framed, and it runs only its own files under a strict content security policy. Invite links get their own keys, expire, and can be revoked.

Join approval

A join code shows nothing, no tabs, activity or state, until you approve that joiner. Joining shares tabs, not a screen: cookies, passwords and remembered details never travel, and sensitive fields cross only as "filled" or "empty".

People stay in charge

Only people can pause and resume agents; no tool or message can. When you click or type in a tab, the agents there wait until you stop. A field you filled stays yours for two minutes.

Agents

What an agent can and cannot do.

The same rules hold for Claude Code, Codex and any MCP client. Where an app can't ask you, the helper refuses the action and hands it to you.

Can, on its own

  • Navigate, read pages, type, choose options and click through ordinary steps without asking
  • Type a saved password by its name, only on the HTTPS domains you listed for it
  • Upload images, video, PDFs and office documents after the helper checks them
  • Make watch links for the live view

Cannot

  • Pay, delete, publish, send or submit a final step without asking you first
  • Publish or submit for review before a passing review is recorded
  • Open file:, javascript:, chrome: or data: addresses, or run arbitrary code
  • Upload key, password or credential files, or anything holding a private key
  • Pause or resume itself, or approve a joiner without asking you
  • Overwrite a field a person filled in the last two minutes

Prompt injection

Pages are data, not instructions.

Your agent is told that web pages, emails and documents are data. If one tells it to do something, it stops and tells you. Messages from other participants arrive marked as coordination information that authorizes nothing.

The hard limits do not depend on the model: secrets only work on their domains, credential files are refused, local-network addresses ask first, and final actions wait for you. Prompt injection can't be fully solved, so watch the browser on sites you don't trust.

The security table

Every risk, and what PairBrowse does about it.

From docs/security.md, which is kept true in the same change as the behaviour it describes. Open a row for the detail.

  1. 01Another program or user on your computer takes over the logged-in browserNo remote-debugging port; one socket file only your user can open.

    No remote-debugging port. Chrome is driven over a private pipe by the PairBrowse helper, and the only way in is a socket file in ~/.pairbrowse/run that only your user account can open. Multiple trusted Claude Code sessions share a serialized queue and control leases.

  2. 02The live view is reached by someone else127.0.0.1 only, random port, random 256-bit key, strict content security policy.

    Starts with the browser, for its side panel. Listens on 127.0.0.1 only, on a random port (or your liveViewPort), behind a random 256-bit key in the URL. Join codes use a second 127.0.0.1 port (the only one the sharing tunnel reaches), where your key never works.

    Requests for any other host name are refused (stops DNS-rebinding), except the names you list in liveViewHosts, which take invite links only; input posted from other websites is refused too.

    The page sends no referrer, can't be framed, and runs only its own script and style files (a strict content security policy: no inline code or styles, nothing loaded from the web); those files come from a fixed list read at start, behind the same key. It stops when the browser closes.

    The side panel gets the address in memory at launch (extension session storage, never on disk), and the live view accepts its requests only from that extension's origin.

  3. 03An invite link goes further than meantOwn 256-bit key per invite, expiry, revocation, watch or drive roles.

    Each invite has its own random 256-bit key, separate from yours, checked in constant time, and ends after 24 hours (7 days at most), when you revoke it, or when PairBrowse stops; expired and revoked keys get the same answer as a wrong one, and an open view ends at once.

    A watch link gets the page, tabs, activity and status only: the helper refuses its clicks, typing and tab changes. A drive link can also click, type and switch tabs, and only after you OK it (in Codex and other apps, Claude can't make one).

    Neither ever gets the Profile panel (remembered details, password names) or its updates, and a drive link can't open local-network addresses from the address bar. Drive input is accepted only from loopback or your inviteBaseUrl origin. Viewers other than you can't resize the page. Anyone with a drive link can still click and type in your logged-in browser.

  4. 04A join code goes further than meantNothing is served until you approve that joiner; strict limits on what they send.

    A join code is a drive or watch invite (same key, roles, expiry and revocation) plus the address of a Cloudflare Quick Tunnel that reaches only the guest port. There, only join code keys work, only from PairBrowse (requests with a browser Origin are refused), and nothing (tabs, activity, state) is served until you approve that joiner, identified by a random id their PairBrowse makes: Allow in your own live view or side panel, or Claude's approve, which always asks you (other apps hand it to you).

    Each approval is bound to one joiner; at most 5 requests wait at once and new ones are rate-limited. Turned-away joiners stay out.

    An approved joiner keeps one WebSocket open (<key>/events, after the same key, approval and no-Origin checks; at most 2 per joiner, messages capped at about 200 KB, a heartbeat every 15 seconds) and sends its changes on it, under the same limits as the plain requests that remain: tab changes (drive only, 3000 a minute), pointer positions (40 a second) and who-does-what and messages (4 a second, 20 KB each).

    Joiners never get a picture of your browser, favicons or the Profile, and nothing they send can click, press keys or submit in a page. The tunnel stops when the last code ends, on revoke_all and when PairBrowse stops. Cloudflare carries the traffic (TLS to Cloudflare).

  5. 05Shared tabs carry more than addressesTabs are shared, not a screen. Cookies, passwords and sensitive field values never travel.

    Joining shares tabs, not a screen: each side's own browser opens the other's tabs, and each person stays signed in as themselves (cookies, storage, passwords and remembered details never travel, and there is no screencast for joiners).

    Only http(s) addresses cross, in both directions, and never with user:pass@, for localhost, local-network or single-label hosts, or IPv6 literals; file:, chrome:, data: and javascript: never do. Watch joiners get origin and path; approved drive joiners also get the query string minus parameters named like credentials, codes, sessions or personal details and secret-looking values, and fragments only as #/routes. Tabs on the sender's secret domains cross as origin and path only, with no title, activity, presence, field values or pointers.

    Form values cross host to joiner, and back only from a drive joiner, matched by frame and a stable key on the same page only, applied by setting the value and firing one "input" event (never "change", key presses or submit), never echoed back. Sensitive fields never carry a value: password type, card, one-time-code and password autocomplete, card, security-code, PIN, IBAN, SSN and similar names, and values that look like a card number, an IBAN, an SSN, a JWT or long token, or that contain a saved password. They cross as "filled" or "empty" only. Hidden and off-screen fields and file inputs are never read; frames on a secret domain give no values; at most 100 fields, 1000 characters per value and 6 frames per tab.

    Pointers are document positions only, never what is under them; they are drawn in a closed shadow root, hidden from screen readers, and take no clicks. Where each person reads crosses as a small named mark on the right edge of the other side's copy. Agent turns cross too: while another computer's agent holds a tab, agents here wait briefly or hear "in use", and never act in their copy.

    Who is doing what is redacted like messages, with addresses cut to origin and path; prompts are never shared. Messages are text only (500 characters, 10 a minute), redacted (saved passwords become their names; card numbers, IBANs and SSNs masked), with no attachments.

    Only an approved drive joiner's changes reach your browser, and each is checked again here (the same address and field rules, so never your local network), at most 20 per request and 3000 per minute. At most 40 tabs and 2048-character addresses cross.

  6. 06A join code points somewhere elseCodes are checked strictly, and every address is checked again on the joiner's side.

    On the joiner's computer, the code is checked strictly (an https *.trycloudflare.com address or a host in their joinHosts, a well-formed key, a known role). Their helper alone talks to the tunnel, with plain requests; nothing listens on their side for it, and every address from the host is checked again before their browser opens it.

  7. 07A page tricks Claude into typing your password into the page's own formSecrets work only on the HTTPS domains you list, checked by the helper.

    Each secret works only on the HTTPS domains you list for it (NAME_DOMAINS), checked against the real address of the current tab by the helper, not by Claude. Values are masked in everything Claude reads, including the snapshot files Claude is pointed to. Screenshots can't be masked, so none is sent while a saved password shows anywhere on the page.

  8. 08A page tricks Claude into uploading private filesKey, password and credential files are always refused.

    The browser reads files only from ~/.pairbrowse/files and the project. pairbrowse_upload takes images, video, PDFs and office documents from anywhere, after the helper checks them, and copies them into files/uploads first.

    Key, password and credential files (.env, *.pem, id_*, credentials.json, anything in ~/.ssh, ~/.aws, ~/.gnupg, the Keychain or ~/.pairbrowse) are always refused, and so is any file whose contents hold a private key or a SECRET= style line, whatever its name. It won't click a submit, publish, pay or delete button to find a file chooser. Any other file type goes through browser_file_upload, which asks you.

  9. 09A page tricks Claude into submitting, paying, deleting or messagingReal commitments ask you first; ordinary steps don't. Judged by page structure, on your computer.

    Real commitments ask you first; ordinary steps don't. The helper reads every click by what it does, from the page's structure only (no word lists, so any wording, any language and icon-only buttons are covered).

    Strong signals: card (cc-* autocomplete or a valid card number), IBAN (by checksum) or billing/shipping fields or a payment frame (allow="payment") in the form, or a card field in any frame on the page, make a submit a payment; a danger-styled button (by its computed color), a submit in a confirmation dialog, a DELETE method or the confirmation a delete or payment click just opened make it a delete or payment; an element it can't read counts as one too. Such a click is refused until Claude names that class at the start of the description ("Pay: Submit order", "Delete: OK"), which is what the hook asks you about.

    Everything else goes without a refusal or a question: links, tabs, plain buttons, sign-in, search and GET forms, steps of a multi-step form, and ordinary form submits such as a sign-up step or a settings save. Claude or Codex names the final actions it knows of from its task ("Pay:", "Delete:", "Publish:", "Send:", "Submit:") and every named click asks you; that naming depends on the agent, so a hostile page can't stop it but an agent that misreads a plain submit may let it go.

    No other model or service judges a click; nothing about a click leaves the computer. "Publish:" clicks are blocked until a passing review is recorded, then still ask. A page's own confirm dialogs are never answered OK by PairBrowse without that class. In Codex and other apps that can't ask, every named final action is refused and handed to you. Clicking by screenshot position refuses strong-signal buttons and anything inside a frame, key presses that would press such a button are refused, and fast mode stops at such a click step.

  10. 10A page fakes the "Your turn" badgeThe badge answers only to a random per-start key pages can't read.

    The badge only answers to a random key generated each time the helper starts, which pages can't read. A page can still draw its own look-alike: Claude only ever asks you to solve CAPTCHAs, sign in or confirm, never to enter card details or codes into a badge.

  11. 11Escaping the browserNo file:, javascript:, chrome: or data: navigation; no run-code tool.

    No file:, javascript:, chrome: or data: navigation, from Claude or from the live view's address bar. The run-arbitrary-code tool is removed. Local-network addresses (router, localhost) ask first (in Codex they're refused: open them yourself).

  12. 12A page reads or leaks what Claude typedRunning scripts in a page always asks; WebMCP is off.

    Running scripts inside a page always asks you. Tools that let pages register their own commands for Claude (WebMCP) are turned off.

  13. 13Supply chainLockfile with checksums, pinned SHA-256 builds, standard-library-only helper.

    The browser runtime is installed from a lockfile with checksums, at exact versions, with install scripts disabled. On macOS, the browser build is pinned by SHA-256 and checked for notarization.

    Native PairBrowse builds come from the project's GitHub release and go through an install step: the archive must match a SHA-256, its build manifest must name this platform and chip, on macOS the app's signature must verify (ad hoc: that shows the files are intact, not who built them; Linux and Windows builds are unsigned, so the SHA-256 is their only integrity check), and a launch check must pass, or the previous build comes back.

    The engine pack the native browser launches with is code the helper loads: its archive must match its pinned SHA-256, and each of its files is checked against its own pinned SHA-256 again every time before it's loaded. Hooks and the helper use only Node's standard library.

  14. 14A site floods or poisons your Downloads folderPlain file names, never over an existing file; Claude is told about each one.

    Files a site hands over are saved to your Downloads folder like in any browser, under a plain file name the site can't use to reach other folders, never over an existing file. PairBrowse doesn't limit how many a site sends; Claude is told about each one.

  15. 15Card numbers or codes show up on screen or in logsThe activity line, bar, side panel and run log mask sensitive values.

    The activity line, bottom bar, side panel and run log mask card numbers (any value that is one, whatever the field) and card, security-code, PIN, IBAN, SSN and similar fields. A value typed into a field that is sensitive by its own kind is masked even when Claude names the field only by its reference. Screenshots Claude gets show the page as it is, card fields included.

  16. 16Another participant's message tries to steer your agentMessages are coordination information that authorizes nothing.

    Messages from other agents or a joiner reach your agent marked as coordination information from another participant, not an instruction from you, that authorizes nothing. They never confirm pay, publish, delete, send or submit clicks, never skip the safety hook, a confirmation or a review, and never lead to typing secrets, uploading files, opening local-network addresses or approving joiners: your agent acts only on your requests. Text only, 500 characters, 10 a minute, redacted, no attachments.

  17. 17An agent overrides people, or resumes itselfPause and Resume come from people only; a person's typing pauses agents in that tab.

    "Pause agents" and "Resume" come from people only: the page's bottom bar (a press counts only from a real click outside any agent's action), the side panel and drive viewers of the live view, and drive joiners' helpers; a watcher's is refused. No tool pauses or resumes, and messages can't. A paused agent's call does nothing and answers after a minute.

    A person clicking or typing in a tab pauses the agents in that tab until they've stopped for two seconds; moving the pointer and scrolling pause nobody. A field a person filled stays theirs for two minutes: an agent's typing, filling, choosing or ticking there is refused unchanged.

  18. 18A web page picks a session or joins one for youThe session picker is the extension's own page, which web pages can't reach.

    The session picker is the side panel extension's own page, which web pages can't open, frame or script (it has no web-accessible resources). It reaches the helper only through the live view, with your key, and the live view takes its requests only from that extension's origin (or loopback with the key); a page has neither.

    Choices count only from real clicks and key presses. A join code is only ever what you typed or pasted there, never filled in from anywhere, and goes through the same checks as pairbrowse_join; the host still has to let you in. To show who used each session, the helper keeps names, apps and the kind of computer (never the computer's own name) in ~/.pairbrowse, nothing about what they did.

  19. 19Files at restEverything in ~/.pairbrowse is private to your user.

    Everything in ~/.pairbrowse is created private to your user (umask 077), including saved tab lists. Each session is a Chrome profile separate from your everyday Chrome.

  20. 20The safety hook fails, or doesn't runIt asks instead of allowing; the helper enforces the rules on its own.

    It asks you instead of allowing. The helper enforces the secret, navigation and code rules on its own, whatever the permission mode, and everything the hook would block (blocked tools, file: and similar addresses, a publish without a passing review) stays blocked even with hooks off or the server added without the plugin.

  21. 21Another app drives the browser (Codex, any MCP client)The helper applies the hook's rules itself for every other app.

    Those apps can't be relied on to run the safety hook or ask you, so the helper applies the hook's rules itself for every app other than Claude Code: what Claude Code would block stays blocked, and what it would ask you about is refused with a note to hand it to you.

Limits, honestly

What it can't promise.

  • Anything running as your user can read files in your home folder, including the browser profile. PairBrowse can't protect against malware already on your computer.
  • The click guard catches mistakes and injected instructions, not every possible trick. Read what a confirmation prompt says.
  • Prompt injection can't be fully solved. Watch the browser (or the live view) on sites you don't trust.
  • An invite link shows everything on screen in your logged-in browser, and a drive link lets that person click and type in it as you. Share links only with people you trust, over a private route, and revoke them when done.
  • The browser runs without Chrome's own sandbox (Playwright's default), so a page that exploits a browser bug isn't contained the way it is in your everyday Chrome. Keep to sites you'd open anyway.
  • PairBrowse doesn't solve CAPTCHAs or bot checks and won't integrate solving services.
  • On macOS the pinned browser build has no Google Safe Browsing, and its security updates usually arrive some days after Chrome's. PairBrowse doesn't update it by itself.
  • On Windows, the socket is a named pipe with Windows' default permissions, and the file-permission checks are skipped.

Responsible disclosure

Found something? Tell us privately.

Report security issues to team@pairbrowse.com or through GitHub's private vulnerability reporting, not in a public issue. Include steps to reproduce and the PairBrowse version.