Skip to main content
Glama
sandbox.ts3.85 kB
/** * Sandbox proxy for secure MCP App isolation. * * This runs in an outer iframe on a separate origin (port 8081). * It creates an inner iframe for the untrusted MCP App HTML content. * Messages are relayed bidirectionally between the host and the app. */ type SandboxProxyReadyNotification = { method: "ui/notifications/sandbox-proxy-ready" }; type SandboxResourceReadyNotification = { method: "ui/notifications/sandbox-resource-ready" }; const ALLOWED_REFERRER_PATTERN = /^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/; if (window.self === window.top) { throw new Error("This file is only to be used in an iframe sandbox."); } if (!document.referrer) { throw new Error("No referrer, cannot validate embedding site."); } if (!document.referrer.match(ALLOWED_REFERRER_PATTERN)) { throw new Error( `Embedding domain not allowed in referrer ${document.referrer}.`, ); } // Security self-test: verify iframe isolation is working try { window.top!.alert("If you see this, the sandbox is not setup securely."); throw "FAIL"; } catch (e) { if (e === "FAIL") { throw new Error("The sandbox is not setup securely."); } } // Create inner iframe for untrusted content const inner = document.createElement("iframe"); inner.style.cssText = "width:100%; height:100%; border:none;"; inner.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms"); document.body.appendChild(inner); const RESOURCE_READY_NOTIFICATION: SandboxResourceReadyNotification["method"] = "ui/notifications/sandbox-resource-ready"; const PROXY_READY_NOTIFICATION: SandboxProxyReadyNotification["method"] = "ui/notifications/sandbox-proxy-ready"; // Build CSP meta tag from domains function buildCspMetaTag(csp?: { connectDomains?: string[]; resourceDomains?: string[] }): string { const resourceDomains = csp?.resourceDomains?.join(" ") ?? ""; const connectDomains = csp?.connectDomains?.join(" ") ?? ""; const directives = [ "default-src 'self'", `script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: ${resourceDomains}`.trim(), `style-src 'self' 'unsafe-inline' blob: data: ${resourceDomains}`.trim(), `img-src 'self' data: blob: ${resourceDomains}`.trim(), `font-src 'self' data: blob: ${resourceDomains}`.trim(), `connect-src 'self' ${connectDomains}`.trim(), "frame-src 'none'", "object-src 'none'", "base-uri 'self'", ]; return `<meta http-equiv="Content-Security-Policy" content="${directives.join("; ")}">`; } // Message relay between host and inner iframe window.addEventListener("message", async (event) => { if (event.source === window.parent) { if (event.data && event.data.method === RESOURCE_READY_NOTIFICATION) { const { html, sandbox, csp } = event.data.params; if (typeof sandbox === "string") { inner.setAttribute("sandbox", sandbox); } if (typeof html === "string") { let modifiedHtml = html; if (csp) { const cspMetaTag = buildCspMetaTag(csp); console.log("[Sandbox] Injecting CSP:", cspMetaTag); if (modifiedHtml.includes("<head>")) { modifiedHtml = modifiedHtml.replace("<head>", `<head>\n${cspMetaTag}`); } else if (modifiedHtml.includes("<head ")) { modifiedHtml = modifiedHtml.replace(/<head[^>]*>/, `$&\n${cspMetaTag}`); } else { modifiedHtml = cspMetaTag + modifiedHtml; } } inner.srcdoc = modifiedHtml; } } else { if (inner && inner.contentWindow) { inner.contentWindow.postMessage(event.data, "*"); } } } else if (event.source === inner.contentWindow) { window.parent.postMessage(event.data, "*"); } }); // Notify host that sandbox is ready window.parent.postMessage({ jsonrpc: "2.0", method: PROXY_READY_NOTIFICATION, params: {}, }, "*");

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jamesdowzard/mcp-apps-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server