diff --git a/mcpjam-inspector/server/routes/apps/mcp-apps/sandbox-proxy.html b/mcpjam-inspector/server/routes/apps/mcp-apps/sandbox-proxy.html index 29f9243c5..67ef89677 100644 --- a/mcpjam-inspector/server/routes/apps/mcp-apps/sandbox-proxy.html +++ b/mcpjam-inspector/server/routes/apps/mcp-apps/sandbox-proxy.html @@ -91,18 +91,33 @@ * @returns {string} CSP policy string */ function buildCSP(csp) { + // Inspector is a dev tool — auto-allow localhost connections. + // In srcdoc iframes, 'self' resolves to about:srcdoc (useless), + // so we must explicitly allow localhost/127.0.0.1 for dev server + // connections (Vite HMR, API server, local MCP servers, etc.) + var localhostSrc = [ + "http://localhost:*", + "https://localhost:*", + "http://127.0.0.1:*", + "https://127.0.0.1:*", + ].join(" "); + var localhostConnectSrc = + localhostSrc + + " ws://localhost:* wss://localhost:* ws://127.0.0.1:* wss://127.0.0.1:*"; + // Per SEP-1865: If no CSP declared, use restrictive defaults // Note: 'self' doesn't work in srcdoc iframes (refers to about:srcdoc) // So we use 'unsafe-inline' for scripts/styles since all widget code is inline if (!csp) { return [ "default-src 'none'", - "script-src 'unsafe-inline'", + "script-src 'unsafe-inline' 'unsafe-eval'", "style-src 'unsafe-inline'", - "img-src data:", - "font-src data:", - "media-src data:", - "connect-src 'none'", + "img-src data: " + localhostSrc, + "font-src data: " + localhostSrc, + "media-src data: " + localhostSrc, + "worker-src blob: " + localhostSrc, + "connect-src " + localhostConnectSrc, "frame-src 'none'", "object-src 'none'", "base-uri 'none'", @@ -111,68 +126,48 @@ // Build CSP from declared domains (SEP-1865) // Per spec: "Host MAY further restrict but MUST NOT allow undeclared domains" - const connectDomains = (csp.connectDomains || []) + var connectDomains = (csp.connectDomains || []) .map(sanitizeDomain) .filter(Boolean); - const resourceDomains = (csp.resourceDomains || []) + var resourceDomains = (csp.resourceDomains || []) .map(sanitizeDomain) .filter(Boolean); - const frameDomains = (csp.frameDomains || []) + var frameDomains = (csp.frameDomains || []) .map(sanitizeDomain) .filter(Boolean); - const baseUriDomains = (csp.baseUriDomains || []) + var baseUriDomains = (csp.baseUriDomains || []) .map(sanitizeDomain) .filter(Boolean); - // connect-src: Only allow declared domains, or 'none' if empty - const connectSrc = - connectDomains.length > 0 ? connectDomains.join(" ") : "'none'"; + // connect-src: declared domains + localhost (dev tool) + var connectSrc = + connectDomains.length > 0 + ? localhostConnectSrc + " " + connectDomains.join(" ") + : localhostConnectSrc; - // Resource sources: data: and blob: are always allowed for inline content - // Only add declared resourceDomains - no forced CDNs per SEP-1865 - const resourceSrc = + // Resource sources: data:, blob:, and localhost always allowed + // Plus declared resourceDomains + var resourceSrc = resourceDomains.length > 0 - ? ["data:", "blob:", ...resourceDomains].join(" ") - : "data: blob:"; + ? ["data:", "blob:", localhostSrc, ...resourceDomains].join(" ") + : "data: blob: " + localhostSrc; // frame-src: Only allow declared frame domains, or 'none' if empty - const frameSrc = + var frameSrc = frameDomains.length > 0 ? frameDomains.join(" ") : "'none'"; // base-uri: Only allow declared base URI domains, or 'none' if empty - const baseUri = + var baseUri = baseUriDomains.length > 0 ? baseUriDomains.join(" ") : "'none'"; - console.log("[buildCSP] Processing domains:", { - frameDomains, - frameSrc, - baseUriDomains, - baseUri, - }); - - console.log( - "[buildCSP] Built CSP string:", - [ - "default-src 'none'", - "script-src 'unsafe-inline' " + resourceSrc, - "style-src 'unsafe-inline' " + resourceSrc, - "img-src " + resourceSrc, - "font-src " + resourceSrc, - "media-src " + resourceSrc, - "connect-src " + connectSrc, - "frame-src " + frameSrc, - "object-src 'none'", - "base-uri " + baseUri, - ].join("; "), - ); - return [ "default-src 'none'", - "script-src 'unsafe-inline' " + resourceSrc, + "script-src 'unsafe-inline' 'unsafe-eval' " + resourceSrc, "style-src 'unsafe-inline' " + resourceSrc, "img-src " + resourceSrc, "font-src " + resourceSrc, "media-src " + resourceSrc, + "worker-src blob: " + resourceSrc, "connect-src " + connectSrc, "frame-src " + frameSrc, "object-src 'none'",