Skip to content

fix(chat): keep the Goose host secret out of MCP app frames - #53

Draft
kalvinnchau wants to merge 3 commits into
mainfrom
cid/secure-mcp-sandbox-proxy
Draft

fix(chat): keep the Goose host secret out of MCP app frames#53
kalvinnchau wants to merge 3 commits into
mainfrom
cid/secure-mcp-sandbox-proxy

Conversation

@kalvinnchau

@kalvinnchau kalvinnchau commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces the Goose host-wide credential in MCP app iframe inputs with a host-minted, short-lived, one-use capability bound to one document. The Tauri broker redeems that capability server-side, validates the requested document and pinned proxy template, and serves guest content from a separate loopback origin.

Problem

The renderer previously embedded the Goose secretKey in the MCP proxy iframe URL as ?secret=. An MCP document could read its own URL and replay or exfiltrate that host-wide bearer through app-selected script or network access.

Changes

  • Add an in-process loopback broker that keeps the Goose host credential in trusted Rust code.
  • Mint one-use capabilities through an app-frame-only Tauri command bound to the owning webview, IPC nonce, document URI, and document digest.
  • Reject replay, expiry, route/origin mismatch, document mismatch, and pinned Goose proxy-template drift.
  • Serve untrusted MCP documents from a separate loopback origin and preserve the existing rendering and CSP contract.
  • Remove the host secret from renderer-facing MCP APIs and iframe construction.
  • Pin the checked proxy fixture to the Goose commit in goose-backend.lock.json.

Validation

At exact head 2ffbee26f43c88ed108b0014d9906cf2d78239a5 with reviewed merge-base a3078aeb160872e28ce85da8a7d6888dec46d5bc:

  • just test: 533 files passed, 6,232 tests passed, 1 skipped.
  • just check: passed.
  • just tauri-check: passed.
  • cargo test --lib services::mcp_app_proxy: 16/16 passed.
  • Push gate: frontend formatting/checks, Tauri checks, and all four clippy invocations passed.
  • Independent exact-head source reviews found no remaining findings.

Residual risk

The trusted broker-to-Goose GET still carries the host bearer in its internal loopback URI because the pinned Goose route accepts query authentication only. That URI is not exposed to the MCP document. Moving this hop to header-only authentication requires a separate upstream Goose change.

A packaged WebView end-to-end flow was not exercised; validation covers source contracts, frontend behavior, Rust behavior, and proxy integration tests.

peon and others added 3 commits August 15, 2026 11:09
… secret

The MCP app proxy iframe URL previously carried the Goose host-wide
secret as a `?secret=` query parameter, exposing the host bearer to an
untrusted MCP document that could read its own URL and replay or
exfiltrate the credential.

Introduce an in-process loopback broker that mints short-lived, one-use
capabilities bound to a single document. The renderer requests a proxy
URL through a trusted Tauri command gated on an app-frame-only IPC nonce
and an app-owned webview identity; the broker holds the host secret,
redeems each capability once server-side, and rewrites the pinned Goose
proxy template so no host secret reaches the iframe URL, DOM, document
JavaScript, logs, or referrers. Guest documents are served from a
separate loopback origin, and capabilities fail closed on replay,
expiry, template drift, origin/route mismatch, or document-digest
mismatch.

Co-authored-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
…dated

The pinned Goose /mcp-app-proxy GET route authenticates only via the
secret query parameter, so the broker cannot move that credential out of
the URL without an upstream Goose change. Document the constraint next to
build_upstream_proxy_url and scope it: this hop is trusted host code to
the trusted Goose backend over loopback and carries no untrusted-document
exposure, so the renderer-facing boundary is unchanged.

Co-authored-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
The rebased branch inherited block/main's goose-backend.lock.json, which pins goose at 11deb564, while the MCP app sandbox broker still pinned the prior 1c1bd529 commit. The pinned proxy template is byte-identical across both commits (9463 bytes, sha256 d7cc6970...), so this only re-aligns the provenance constant and fixture name; the runtime template-drift rejection and digest assertion are unchanged.

Co-authored-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant