Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/chrome-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
import { tidy } from "./anchor-text.js";
import { pageUrl, replacePage } from "./chrome-session.js";
import { normalizeHref } from "./editing.js";
import { framePolicy } from "./frame-policy.js";

const $ = (id) => document.getElementById(id);
Expand Down Expand Up @@ -607,9 +608,14 @@ window.addEventListener("message", async (event) => {
case "eh:scroll":
state.scroll = { x: msg.x, y: msg.y };
break;
case "eh:external":
window.open(msg.href, "_blank", "noopener");
case "eh:external": {
// This side is what actually calls window.open, so it re-checks the
// scheme rather than trusting the frame: a javascript: or data: URL
// arriving here would run on this origin, next to the token.
const external = normalizeHref(msg.href);
if (external) window.open(external, "_blank", "noopener");
break;
}
case "eh:navigate":
try {
const result = await api(`/api/session/${state.sessionId}/navigate`, {
Expand Down
2 changes: 1 addition & 1 deletion src/chrome.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<iframe
id="frame"
title="Artifact under review"
sandbox="allow-scripts allow-forms allow-modals allow-popups allow-downloads"></iframe>
sandbox="allow-scripts allow-forms allow-modals"></iframe>
</main>

<button type="button" id="handle" class="handle" title="Hide comments panel" aria-label="Hide comments panel">›</button>
Expand Down
2 changes: 1 addition & 1 deletion src/frame-policy.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const BASE_SANDBOX = "allow-scripts allow-forms allow-modals allow-popups allow-downloads";
const BASE_SANDBOX = "allow-scripts allow-forms allow-modals";

/**
* Localhost apps need their real origin so their routing and JavaScript work.
Expand Down
11 changes: 11 additions & 0 deletions test/frame-policy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,14 @@ test("file and Markdown reviews use an opaque origin", () => {
assert.equal(policy.targetOrigin, "*");
}
});

test("no review grants the frame popups or downloads", () => {
// The SDK hands external links to the chrome over postMessage, which opens
// them itself, so the frame never needs allow-popups. Nothing needs
// allow-downloads. Untrusted artifacts get neither.
for (const page of [{ kind: "url" }, { kind: "file", markdown: false }, { kind: "file", markdown: true }]) {
const policy = framePolicy(page, artifactOrigin);
assert.doesNotMatch(policy.sandbox, /\ballow-popups\b/);
assert.doesNotMatch(policy.sandbox, /\ballow-downloads\b/);
}
});