sequenceDiagram
autonumber
participant Host as Host frame (app.slack.com)
participant Proxy as Proxy frame (file-id.slack-mcps.com)
participant Inner as Inner iframe (LLM-authored HTML)
Note over Host: html-surface-iframe.tsx
Note over Proxy: McpSandboxProxyController
Note over Inner: Same origin as Proxy
Note over Host: buildReportSandbox(fileId, csp)<br/>derives per-file origin + ?csp= param
Host->>Proxy: load iframe src=<br/>file-id.slack-mcps.com/mcp-sandbox-proxy?csp=…<br/>sandbox="allow-scripts allow-same-origin"
Note over Proxy: Server sets tamper-proof<br/>Content-Security-Policy HTTP header<br/>(frame-ancestors app.slack.com)<br/>?csp= re-validated server-side
Note over Proxy: Reject if window.self === window.top<br/>HOST_ORIGIN injected server-side
Proxy-->>Host: postMessage(PROXY_READY)<br/>target = HOST_ORIGIN
Note over Host: handler validates<br/>event.origin === sandbox.origin<br/>AND event.source === iframe.contentWindow
Host->>Proxy: postMessage(RESOURCE_READY,<br/>{params:{html: patchedHtml}})<br/>target = sandbox.origin
Note over Proxy: handler validates<br/>event.origin === HOST_ORIGIN<br/>AND source === parent (or parent.opener)
Proxy->>Inner: document.write(html) into inner iframe<br/>(origin inherited, no navigation)
Note over Inner: LLM code renders<br/>(React/Babel or vanilla + Chart.js)
Inner--xHost: any postMessage from LLM code<br/>Nobody listens — no inbound API
flowchart TB
subgraph S1["Origin A: app.slack.com (TRUSTED)"]
Host["Host frame — html-surface-iframe.tsx\nholds shell HTML + live data\nlistens for PROXY_READY only"]
end
subgraph S2["Origin B: file-id.slack-mcps.com (per-file isolated)"]
Proxy["Proxy frame — Hack controller\nstateless relay\nsets CSP header, validates HOST_ORIGIN"]
Inner["Inner iframe — SAME origin as proxy\nLLM-authored HTML (UNTRUSTED)\npartitioned localStorage/IDB/cookies"]
Proxy -- "document.write()" --> Inner
end
Host == "① iframe src + ?csp= hint, re-validated" ==> Proxy
Proxy == "② PROXY_READY contentless signal" ==> Host
Host == "③ RESOURCE_READY html to sandbox.origin" ==> Proxy
Inner -. "✗ no inbound RPC: host ignores all messages from LLM code" .-> Host
classDef trusted fill:#1d6f42,stroke:#0b3d24,color:#fff
classDef untrusted fill:#8a1c3b,stroke:#4d0f20,color:#fff
classDef relay fill:#3b4a8a,stroke:#1f2a4d,color:#fff
class Host trusted
class Inner untrusted
class Proxy relay