Skip to content

MCP side does not pin the extension's identity — freshness without continuity #208

Description

@chrischall

The trust relationship between an MCP and the extension is asymmetric, and the asymmetry is invisible on loopback.

The extension pins the MCP. trustedMcps is keyed by SHA-256 of the MCP's X25519 identity pub; a returning MCP with a different identity is a re-pair prompt, and a widened domains/capabilities/graphqlOps set is a re-pair with a diff.

The MCP does not pin the extension. host.ts verifies ready.sessionSigEd25519Sign(extEdPriv, mcpHelloNonce || extHelloNonce) — against identityEd25519Pub from the extension hello received in the same connection. That proves the connecting party holds the private key it just presented. It does not prove it is the same party as last time, because nothing from last time is retained. peer.ts doesn't even do that: it derives from ready.extensionSessionPub and verifies no identity at all.

So today the MCP's answer to "which browser is this?" is "whichever one said hello", every time.

On loopback that's fine and deliberate — the only thing that can reach 127.0.0.1:37149 is a local process, and docs/SECURITY.md scopes the model to single-user, single-host accordingly.

Why it's worth closing now

chrischall/mcp-host is exploring hosting the ~13 browser-bridge MCPs by moving the concentrator off the laptop (chrischall/mcp-host#162, design in docs/BROWSER-BRIDGE.md). I've now run the transport-split experiment end to end — a relay dialling a real alltrails-mcp in the extension role, real Chrome extension on the other side, genuine HTTP 200 out of the signed-in tab (chrischall/mcp-host#166).

The relevant finding for this repo: a verbatim-forwarding relay is invisible to the current mutual auth, exactly as designed. The pair code commits to both identity pubs and ready signs both nonces, so relaying the real hellos unmodified verifies on both ends — that's the T-host-MITM property working. What the crypto does not answer is the endpoint: the moment that endpoint is reachable over a network rather than loopback, "whichever browser said hello" means anyone who finds it can present a fresh identity and trigger a pair prompt in someone's browser.

That is why the mcp-host design treats this as a hard precondition for making the extension's target configurable, and orders it strictly before that change rather than alongside it. A configurable remote target without this is an unauthenticated pairing surface.

Shape of the fix

Mirror what the extension already does, in the other direction:

  • persist the extension's identityX25519Pub / identityEd25519Pub alongside the MCP's own identity (~/.fetchproxy/identity/<server-name>.json, or a sibling file) at the point the session first derives — i.e. the moment the user has approved this pairing;
  • on every later connection, refuse a hello whose identity differs from the stored one, rather than accepting it and prompting;
  • give the user a deliberate way out (re-pair / reset), since a legitimate extension re-install mints a new identity and would otherwise brick the MCP;
  • peer.ts should get the same check — today a peer verifies nothing, so a pin only on host.ts leaves the peer path as the soft way in.

Two things worth deciding explicitly rather than by implementation:

  1. First-contact policy. TOFU (trust the first extension identity seen) is the obvious pick and matches the extension's own behaviour, but on a network endpoint the first contact may not be the user's.
  2. Multiple browsers. One user, two machines/profiles is a real case; a single pinned pub forecloses it, a set of them needs its own approval UI.

Separately, and not a gate: a FETCHPROXY_WS_PORT fallback in @fetchproxy/server would collapse the 13 per-MCP port spellings into one and is the only route to the bootstrap path (honeybook constructs FetchproxyServer inside @fetchproxy/bootstrap and never names a port). Filing that separately if this one lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions