pi-passport

Browse the web as yourself from a Pi agent — your real logged-in session, scoped to the sites you grant. Copies only those origins' cookies out of your own Chromium profile into an ephemeral snapshot, drives it with your own browser binary, and gates ever

Packages

Package details

extension

Install pi-passport from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-passport
Package
pi-passport
Version
0.1.0
Published
Aug 28, 2026
Downloads
140/mo · 140/wk
Author
zahnno
License
MIT
Types
extension
Size
86.2 KB
Dependencies
2 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./extensions"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-passport

Let a Pi agent browse the web as you — with your real logged-in sessions — without handing it your whole identity.

The agent gets a browser that is signed into the sites you named, and nothing else. Not a copy of your cookie jar; a copy of the four cookies that belong to the one site you granted. When the session ends the copy is deleted.

/passport allow github.com
> Open my open PRs and tell me which ones have review comments I haven't replied to.

Why "passport"

A passport is a scoped credential. It is checked at each border, it says who you are to the places you present it to, and it is not a master key to your house. That is exactly the grant this package models — as distinct from handing an agent your browser profile, which is every credential you have at once.

It is also not pi-browser, a separate Playwright-backed Pi extension that drives a clean browser. This one is about identity: the browser it drives is yours, signed in as you, to a list of sites you name.

Why this exists

Pointing an agent at your browser profile is easy and almost always wrong. Your profile is one undifferentiated blob of identity: your mail, your bank, your employer's SSO, every session you have ever established. "Let the agent check my PRs" and "let the agent read my email" are the same grant, and nothing about the mechanism can tell them apart.

This package makes the grant per-site and enforces it where it cannot be argued with — by deleting every other cookie out of the copied database before any browser opens it. A page the agent visits cannot reach a session that was never granted, however the model is talked into behaving.

Install

pi packages add pi-passport

Requires Node 22.19+, and Chrome, Edge, Brave or Chromium as your default browser. Playwright is used as the driver only; no browser download is needed, because the browser it launches is yours.

Use

/browse                        status: which browser, which grants, what is open
/passport allow github.com       grant one site
/passport allow mail.google.com  grants google.com's session for mail specifically
/passport revoke github.com      take it back; closes any open session immediately
/passport browser brave          use a specific Chromium instead of the OS default
/passport close                  close the session and delete the copy
/passport off                    remove every grant

Then just ask. The agent gets four tools:

tool what it does
browse_open open a URL, return the page's interactive elements and text
browse_snapshot re-read the current page
browse_act click / fill / type / press / select one element — you approve each one
browse_close close the session and delete the copied profile

The model acts by naming a ref (e7) from the last snapshot, never by writing a selector. A ref can only resolve to something that was actually on the page; an invented selector can resolve to the wrong button.

What it copies, and what it does not

copied not copied
cookies for granted domains only cookies for everything else
Local State (holds the cookie key on Windows) saved passwords (Login Data)
the active profile's Preferences autofill and payment data (Web Data)
history, Local Storage, IndexedDB, extensions

Passwords and autofill can be turned on (includePasswords, includeAutofill in ~/.pi/passport.json) but are off by default and should stay that way: a session cookie is what browsing as you requires, and a password database is a strictly larger grant that nothing here needs.

The copy lives in a fresh 0700 directory under the OS temp dir and is deleted when the session closes, when a grant is revoked, and on process exit. Nothing from your browser is persisted between sessions — the config file holds a list of domain names and three booleans, and that is all.

The rules it will not bend

Attended only. With no interactive UI attached — a routine, a scheduled task, a channel bot — every browse tool refuses. That run cannot be asked whether it meant to click "delete account", and it is also exactly the run whose prompt text nobody wrote by hand, which makes "read a web page, then act on it with the user's live session" a complete injection-to-account-takeover path. allowUnattended exists in the config and should be left alone.

Every action is approved. Reading a granted page is what you asked for. Sending, buying, posting and deleting are not, and from outside a page there is no reliable way to tell a "Save draft" button from a "Send" one. You see the ref, the action and the origin.

Page text is untrusted. Every result carrying page content is fenced and labelled as untrusted input, because it arrives from the open internet into a session that is signed into things.

Grants must be real sites. com is refused (it would match every .com cookie in your jar), and so is githubcom (it would match nothing while looking like it worked).

Failures are refusals. A non-Chromium default browser, a Beta/Dev/Canary channel whose profile directory we can't resolve, a locked profile, a cookie filter that could not be applied — each stops with a reason rather than guessing. Driving the wrong profile means acting as the wrong person.

Three things that are not obvious

Anyone modifying this package should know why these are the way they are. All three were found the hard way, and each one produces a browser that launches perfectly and is silently signed out of everything — a failure that reads as a website problem, not a bug.

1. It launches your browser, not Playwright's. Cookie values are encrypted with a key the OS hands out per application: macOS gates the "Chrome Safe Storage" keychain item by code signature, Linux does the same via gnome-keyring/kwallet, and Chrome 127+ on Windows binds the key to the installed executable's path. Playwright's bundled Chromium opens the same profile and reads every cookie as garbage.

2. Two Playwright default args are removed. Playwright adds --password-store=basic and --use-mock-keychain to every Chromium launch, so automation never trips a keychain prompt. Against a real profile they mean the browser never asks for the key its cookies were sealed with. Measured on a jar of 15 cookies: 0 readable with them, 14 with them removed. They are removed via ignoreDefaultArgs, and scripts/smoke-negative.ts re-proves it.

3. The cookie database layout is mirrored, not assumed. Chromium moved cookies from Default/Cookies to Default/Network/Cookies and both layouts are live in the wild — on the machine this was developed on, a current Chrome uses the old one. The copy is written wherever the source keeps it.

Related: the profile copied is the one named by Local State → profile.last_used, not Default. If your real session lives in Profile 3, reading Default gets you an agent that is signed out of everything.

Honest limits

  • Cookies only. Sites that keep their session in localStorage rather than cookies will appear signed out. Most large sites are cookie-based; some SPAs are not.
  • Not an isolation boundary. Within a granted origin the agent has your session, full stop. The grant limits which identities are exposed, not what can be done with the ones you exposed. Grant narrowly.
  • The window is visible. Headless is refused — Chromium's legacy headless mode keeps a separate cookie store and loads none of the copied jar. Watching an agent act as you is also a feature.
  • Windows needs the browser closed. A running Chrome/Edge/Brave holds its cookie database with a deny-all lock, so the snapshot refuses with a "fully quit it" message rather than hanging. macOS and Linux copy fine while the browser runs.

Development

npm run typecheck
npm test                    # 42 tests, no browser needed
npm run smoke:detect        # what this machine resolves to
npm run smoke:snapshot      # end-to-end: opens a real browser, proves a login carried
npm run smoke:negative      # proves rules 2 and 3 above are load-bearing

MIT.