From e3c7948874efde2d736e4e4a74c909d23c2a56f0 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Mon, 25 May 2026 09:50:20 +0000 Subject: [PATCH] Studio: block tabnabbing from HTML preview popups Codex P1 on the previous commit: granting both ``allow-popups`` and ``allow-popups-to-escape-sandbox`` while forcing every link through ```` lets a malicious assistant-emitted link open a regular browser tab that retains ``window.opener``. The destination page can then call ``window.opener.top.location.href = '...'`` and tabnab the original Studio tab. Drop ``allow-popups-to-escape-sandbox``. ``allow-popups`` stays so the click still produces a tab instead of being silently dropped, but the popup now INHERITS the iframe sandbox (no allow-same-origin, no allow-top-navigation), so it cannot reach back into the host. Trade-off: the opened tab loads with an opaque origin and some real sites render degraded inside the popup. This is the deliberate exchange for tabnabbing safety; the chat-message preview iframe is the primary surface, the popup is a follow-up link click. Existing rendering-iframe sandbox test now asserts allow-popups-to-escape-sandbox is NOT present. --- .../__tests__/html-svg-renderer.test.tsx | 14 ++++++++------ .../assistant-ui/html-svg-renderer.tsx | 19 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) 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,