diff --git a/studio/frontend/src/components/assistant-ui/__tests__/html-svg-renderer.test.tsx b/studio/frontend/src/components/assistant-ui/__tests__/html-svg-renderer.test.tsx index 4de79e7b7c..6cbb635d1b 100644 --- a/studio/frontend/src/components/assistant-ui/__tests__/html-svg-renderer.test.tsx +++ b/studio/frontend/src/components/assistant-ui/__tests__/html-svg-renderer.test.tsx @@ -27,10 +27,16 @@ describe("HtmlSvgRenderer", () => { expect(iframe.tagName).toBe("IFRAME"); // SECURITY: allow-scripts + allow-modals leave script / alert / // confirm operative IF the inherited host CSP ever permits inline; - // NEVER allow-same-origin or allow-top-navigation -- those would let + // allow-popups + allow-popups-to-escape-sandbox so a `` link does not silently no-op; NEVER + // allow-same-origin or allow-top-navigation -- those would let // the preview read parent.document and exfiltrate session data. const sandbox = iframe.getAttribute("sandbox") ?? ""; - expect(sandbox.split(/\s+/)).toContain("allow-scripts"); + const sandboxTokens = sandbox.split(/\s+/); + expect(sandboxTokens).toContain("allow-scripts"); + expect(sandboxTokens).toContain("allow-modals"); + expect(sandboxTokens).toContain("allow-popups"); + expect(sandboxTokens).toContain("allow-popups-to-escape-sandbox"); expect(sandbox).not.toContain("allow-same-origin"); expect(sandbox).not.toContain("allow-top-navigation"); // srcdoc carries the assistant HTML plus a defense-in-depth meta CSP diff --git a/studio/frontend/src/components/assistant-ui/html-svg-renderer.tsx b/studio/frontend/src/components/assistant-ui/html-svg-renderer.tsx index 8664e0469f..612f258916 100644 --- a/studio/frontend/src/components/assistant-ui/html-svg-renderer.tsx +++ b/studio/frontend/src/components/assistant-ui/html-svg-renderer.tsx @@ -346,12 +346,23 @@ function HtmlPreview({ data-testid="html-svg-renderer-iframe" title="HTML preview" srcDoc={srcDoc} - // SECURITY: allow-scripts (in case the host CSP ever loosens to - // permit inline) + allow-modals (so alert/confirm do not silently - // no-op when scripts do run). We do NOT grant allow-same-origin or - // allow-top-navigation, so the iframe cannot read parent.document, - // navigate the host page, or escape its origin. - sandbox="allow-scripts allow-modals" + // SECURITY: + // allow-scripts -- in case the host CSP ever loosens + // to permit inline (today it does not) + // allow-modals -- so alert/confirm/prompt do not no-op + // when scripts do run + // allow-popups + -- so the ```` we + // allow-popups-to- set above can open a new tab without + // escape-sandbox being silently dropped; escape- + // sandbox makes the popup a regular + // browser tab rather than an opaque- + // origin sandboxed one (the popup is + // equivalent to the user clicking the + // same URL in any other web app) + // We do NOT grant allow-same-origin or allow-top-navigation, + // so the iframe cannot read parent.document, navigate the host + // page, or escape its origin. + sandbox="allow-scripts allow-modals allow-popups allow-popups-to-escape-sandbox" style={{ width: "100%", height: iframeHeight,