Studio: let HTML preview links open a new tab

Codex review on b21717120c flagged that the previous commit's
``<base target="_blank">`` combined with sandbox flags that omitted
``allow-popups`` silently dropped every link click in the preview --
before that change links at least navigated inside the frame; after
it they were no-ops.

Add ``allow-popups`` and ``allow-popups-to-escape-sandbox`` so a
target=_blank link opens a regular browser tab (rather than an
opaque-origin sandboxed one that most docs sites would render
broken). The popup is then equivalent to the user clicking the same
URL anywhere else. allow-same-origin and allow-top-navigation are
still NOT granted, so the iframe still cannot read parent.document
or navigate the host page.

Test updated to assert the new sandbox tokens explicitly.
This commit is contained in:
Daniel Han 2026-05-25 07:38:17 +00:00
commit fc28f8486b
2 changed files with 25 additions and 8 deletions

View file

@ -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 `<base
// target="_blank">` 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

View file

@ -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 ``<base target="_blank">`` 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,