diff --git a/client/public/mcp-sandbox.html b/client/public/mcp-sandbox.html index ab384b1639..27e7bb24bc 100644 --- a/client/public/mcp-sandbox.html +++ b/client/public/mcp-sandbox.html @@ -31,6 +31,7 @@ let innerFrame = null; let innerFrameBlobUrl = null; + let innerFrameNavigated = false; let readyInterval = null; const SANDBOX_PREFIX = 'ui/notifications/sandbox-'; @@ -84,13 +85,13 @@ createInnerFrame(msg.params); return; } - if (innerFrame && innerFrame.contentWindow) { + if (innerFrame && innerFrame.contentWindow && !innerFrameNavigated) { innerFrame.contentWindow.postMessage(msg, '*'); } return; } - if (innerFrame && event.source === innerFrame.contentWindow) { + if (innerFrame && !innerFrameNavigated && event.source === innerFrame.contentWindow) { if (msg.method && msg.method.startsWith(SANDBOX_PREFIX)) { return; } @@ -111,6 +112,25 @@ innerFrame = document.createElement('iframe'); + // The first load is the blob document we created. A `allow-scripts` app can navigate its own + // frame; any further load means it left our document, so stop proxying and tear it down + // rather than forward host responses (proxied MCP data) to the navigated page. + innerFrameNavigated = false; + let frameLoads = 0; + innerFrame.addEventListener('load', () => { + frameLoads += 1; + if (frameLoads <= 1) { + return; + } + innerFrameNavigated = true; + if (innerFrameBlobUrl) { + URL.revokeObjectURL(innerFrameBlobUrl); + innerFrameBlobUrl = null; + } + innerFrame.remove(); + innerFrame = null; + }); + // Strip allow-same-origin from the inner frame regardless of what the host requested. // Full cross-origin isolation requires the sandbox proxy to be served from a different // origin than the host; stripping allow-same-origin provides partial mitigation when diff --git a/packages/api/src/mcp/UserConnectionManager.ts b/packages/api/src/mcp/UserConnectionManager.ts index 785dfb6755..e4a8ec0173 100644 --- a/packages/api/src/mcp/UserConnectionManager.ts +++ b/packages/api/src/mcp/UserConnectionManager.ts @@ -453,7 +453,7 @@ export abstract class UserConnectionManager { graphTokenResolver, }); const registry = MCPServersRegistry.getInstance(); - const { allowedDomains, allowedAddresses, useSSRFProtection } = + const { allowedDomains, allowedAddresses, useSSRFProtection, appsEnabled } = await registry.resolveAllowlists({ userId: user?.id, role: user?.role }); await this.assertResolvedRuntimeConfigAllowed({ config: runtimeConfig, @@ -472,7 +472,7 @@ export abstract class UserConnectionManager { useSSRFProtection, allowedDomains, allowedAddresses, - enableApps: registry.getAppsEnabled(), + enableApps: appsEnabled, ephemeralConnection, };