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 a1463ed9d2..2b13345ed2 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 @@ -26,17 +26,19 @@ describe("HtmlSvgRenderer", () => { ) as HTMLIFrameElement; expect(iframe.tagName).toBe("IFRAME"); // SECURITY: allow-scripts + allow-modals leave script / alert / - // confirm operative IF the inherited host CSP ever permits inline; - // 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. + // confirm operative if the inherited host CSP ever permits inline. + // allow-popups lets ```` links open without + // being silently dropped, BUT the popups INHERIT the sandbox + // (allow-popups-to-escape-sandbox is intentionally absent) so a + // tab opened from a malicious assistant link cannot use + // window.opener.top.location.* to tabnab the Studio tab. + // allow-same-origin and allow-top-navigation are NEVER granted. const sandbox = iframe.getAttribute("sandbox") ?? ""; 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(sandboxTokens).not.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 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 c0fb3465ad..d257ab9756 100644 --- a/studio/frontend/src/components/assistant-ui/html-svg-renderer.tsx +++ b/studio/frontend/src/components/assistant-ui/html-svg-renderer.tsx @@ -356,13 +356,18 @@ function HtmlPreview({ // that includes 'unsafe-inline' // allow-modals -- alert/confirm/prompt are not no-ops // when scripts do fire - // allow-popups + -- a ```` link - // allow-popups-to- opens a regular browser tab rather - // escape-sandbox than an opaque-origin sandboxed one - // 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" + // allow-popups -- the ```` link + // rule can open a new tab instead of + // silently dropping the click + // We do NOT grant: + // allow-same-origin / allow-top-navigation -- the iframe + // cannot read parent.document or navigate the host page + // allow-popups-to-escape-sandbox -- popups INHERIT the sandbox + // so an opened tab cannot use ``window.opener.top.location`` + // to tabnab the Studio tab. The opened tab loads with an + // opaque origin (some sites will render degraded) which is + // the deliberate trade-off for tabnabbing safety. + sandbox="allow-scripts allow-modals allow-popups" style={{ width: "100%", height: iframeHeight,