/**
* 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: {},
}, "*");