Skip to content

Commit cfe4a32

Browse files
authored
feat: Allow newtabs and data: images to function in iframe. (#1016)
1 parent 0f6e65e commit cfe4a32

File tree

5 files changed

+53
-20
lines changed

5 files changed

+53
-20
lines changed

iframe-sandbox/README.md

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,12 @@ a sandboxed iframe to execute arbitrary code.
1010
## Goals
1111

1212
To run untrusted code within an iframe, with the ability to communicate with
13-
host to read, write, and subscribe to values in a key value store.
14-
15-
Code within an iframe **MUST NOT** be able to communicate with any third party.
16-
17-
For example, private data could be added to query parameters in a background
18-
image or JS module URL.
19-
20-
In the future, there could be verified authors and domains per frame.
13+
host to read, write, and subscribe to values in a key value store, not allowing
14+
code to communicate with any external third party.
2115

2216
> [!CAUTION]
23-
> During experimental development, there are currently hardcoded CDNs that are
24-
> accessible in the iframe context. Data could be exfiltrated this way to those
25-
> CDNs.
17+
> During experimental development, there are intentional gaps in the sandboxing
18+
> to enable product features where data within the sandbox may be exfiltrated.
2619
2720
## Usage
2821

@@ -70,9 +63,11 @@ frame that propagates to the inner (untrusted) frame across browsers.
7063

7164
## Incomplete Security Considerations
7265

73-
- Currently, the hardcoded CDNs (and their logging services) **MAY** receive
74-
exfiltrated data. We should only allow 1P mediated communications in the
75-
future.
66+
Some of these are shortcomings of implementation, and some are intentional
67+
product decisisons during experimentation.
68+
69+
- Hardcoded CDNs (and their logging services) are an exfiltration vector.
70+
- Allowing anchor elements with `target="_blank"` is an exfiltration vector.
7671
- `document.baseURI` is accessible in an iframe, leaking the parent URL
7772
- Currently without CFC, data can be written in the iframe containing other
7873
sensitive data, or newly synthesized fingerprinting via capabilities

iframe-sandbox/src/common-iframe-sandbox.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ export class CommonIframeSandboxElement extends LitElement {
498498
<iframe
499499
${ref(this.iframeRef)}
500500
allow="clipboard-write"
501-
sandbox="allow-scripts allow-pointer-lock"
501+
sandbox="allow-scripts allow-pointer-lock allow-popups allow-popups-to-escape-sandbox"
502502
.srcdoc=${OuterFrame}
503503
height="100%"
504504
width="100%"

iframe-sandbox/src/csp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export const CSP = `` +
3131
`style-src ${HOST_ORIGIN} 'unsafe-inline' ${STYLE_CDNS.join(" ")};` +
3232
// Fonts: Allow 1P, inline.
3333
`font-src ${HOST_ORIGIN} 'unsafe-inline' ${FONT_CDNS.join(" ")};` +
34-
// Images: Allow 1P, inline.
35-
`img-src ${HOST_ORIGIN} 'unsafe-inline';` +
34+
// Images: Allow 1P, data URIs.
35+
`img-src ${HOST_ORIGIN} data:;` +
3636
// Disabling until we have a concrete case.
3737
`form-action 'none';` +
3838
// Disable <base> element

iframe-sandbox/src/outer-frame.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ export default `
44
<!DOCTYPE html>
55
<html>
66
<head>
7-
<meta http-equiv="Content-Security-Policy" content="${CSP}" \/>
8-
<style>
7+
<meta http-equiv="Content-Security-Policy" content="${CSP}" \/>
8+
<style>
99
html, body {
1010
padding: 0;
1111
margin: 0;
@@ -30,7 +30,7 @@ iframe {
3030
<body>
3131
<iframe
3232
allow="clipboard-write"
33-
sandbox="allow-scripts allow-modals"><\/iframe>
33+
sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-modals"><\/iframe>
3434
<script>
3535
const iframe = document.querySelector("iframe");
3636
const HOST_ORIGIN = "${HOST_ORIGIN}";

iframe-sandbox/test/iframe-csp.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,23 @@ const SCRIPT_URL = "https://common.tools/static/sketch.js";
5959
const STYLE_URL = "https://common.tools/static/main.css";
6060
const IMG_URL = "https://common.tools/static/text.png";
6161
const ORIGIN_URL = new URL(globalThis.location.href).origin;
62+
const BASE64_IMG_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=";
6263

6364
function openWindow(target: any) {
6465
return `<script>
6566
let win = window.open("${HTML_URL}", "${target}");
6667
if (win) throw new Error("Window Opened");</script>`;
6768
}
6869

70+
function clickAnchor(target: string) {
71+
return `
72+
<a id="anchor-test" href="${HTML_URL}" target="${target}">
73+
<script>
74+
const anchor = document.querySelector("#anchor-test");
75+
anchor.click();
76+
</script>`;
77+
}
78+
6979
const cases = [[
7080
"allows inline script",
7181
`<script>console.log("foo")</script><style>* { background-color: red; }</style><div>foo</div>`,
@@ -78,6 +88,10 @@ const cases = [[
7888
"allows 1P img",
7989
`<img src="${ORIGIN_URL}/foo.jpg" />`,
8090
null,
91+
], [
92+
"allows data: img",
93+
`<img src="${BASE64_IMG_URL}" />`,
94+
null,
8195
], [
8296
"allows 1P CSS",
8397
`<link rel="stylesheet" href="${ORIGIN_URL}/styles.css">`,
@@ -98,6 +112,18 @@ const cases = [[
98112
"disallows opening windows (_top)",
99113
openWindow("_top"),
100114
null,
115+
], [
116+
"disallows anchor link target (_parent)",
117+
clickAnchor("_parent"),
118+
null,
119+
], [
120+
"disallows anchor link target (_self)",
121+
clickAnchor("_self"),
122+
null,
123+
], [
124+
"disallows anchor link target (_top)",
125+
clickAnchor("_top"),
126+
null,
101127
], [
102128
"disallows fetch",
103129
`<script>fetch("${SCRIPT_URL}");</script>`,
@@ -161,6 +187,15 @@ const falseNegatives = [[
161187
"CSP:default-src",
162188
]];
163189

190+
// /!\ These tests do not report correctly.
191+
// /!\ Not sure why! But they appear to be allowed
192+
// /!\ but are not in practice.
193+
const falsePositives = [[
194+
"Allows anchor link target (_blank)",
195+
clickAnchor("_blank"),
196+
null,
197+
]];
198+
164199
const unknownStatuses = [
165200
[
166201
// `prerender` is a Chrome-only feature-flagged
@@ -178,6 +213,9 @@ for (const [name, html, expected] of cases) {
178213
for (const [name, html, expected] of falseNegatives) {
179214
definePending(name, html, expected);
180215
}
216+
for (const [name, html, expected] of falsePositives) {
217+
definePending(name, html, expected);
218+
}
181219
for (const [name, html, expected] of unknownStatuses) {
182220
definePending(name, html, expected);
183221
}

0 commit comments

Comments
 (0)