Skip to content
Merged
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
3 changes: 2 additions & 1 deletion docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ there.
- An opaque-origin `srcdoc` document carrying its own `<meta>` CSP
(`default-src 'none'`; `script-src` the loader's hash plus
`'wasm-unsafe-eval'`; `style-src`/`img-src`/`font-src`/`media-src
blob:` — the asset stylesheet is a `blob:`). CSP
blob:` — the asset stylesheet is a `blob:`; `img-src` also `data:`, for
the stylesheet's inline SVG backgrounds, which fetch nothing). CSP
policies compose with the embedder's header policy, so the frame is
network-dead regardless. `sandbox="allow-scripts allow-forms"`;
`form-action 'none'`. The loader is a constant; everything variable
Expand Down
16 changes: 16 additions & 0 deletions e2e/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,22 @@ const scenarios: Scenario[] = [
const background = await todoFrame(page).locator("section.todoapp")
.evaluate((el) => getComputedStyle(el).backgroundColor);
eq(background, "rgb(255, 255, 255)", "the stylesheet did not apply");

// The checkboxes are `data:` SVG background images in that stylesheet
// (todomvc-app.css `.toggle + label`), which `img-src` must admit. A
// computed `background-image` reads the same whether or not CSP let
// the image load, so load a `data:` image in the frame and see.
const dataImageLoads = await todoFrame(page).locator("body").evaluate(
() =>
new Promise<boolean>((resolve) => {
const img = new Image();
img.onload = () => resolve(true);
img.onerror = () => resolve(false);
img.src =
"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'/>";
}),
);
check(dataImageLoads, "the frame's CSP blocks data: images");
},
},

Expand Down
9 changes: 7 additions & 2 deletions web/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,12 @@ function base64(bytes: ArrayBuffer): string {
* cannot reach the network at all. Everything it renders comes down the
* mutation stream or out of a `blob:` the parent minted, which is why
* `style-src` allows `blob:` (the app bundle's stylesheet is an asset, so
* `<link href>` resolves to a blob URL).
* `<link href>` resolves to a blob URL). `img-src` also allows `data:`: the
* bytes of a `data:` URL are inline in whatever names it — here, the
* stylesheet's `background-image` SVGs — so nothing leaves the frame. An
* `<img src="data:...">` from the app is still refused, by the receiver
* policy (web/policy.ts: URL-kind attributes take asset handles only), not
* by this CSP.
*
* The hash covers the exact text between the tags INCLUDING the two
* newlines, because that is what the browser hashes.
Expand All @@ -358,7 +363,7 @@ async function frameSrcdoc(frameJs: string): Promise<string> {
"default-src 'none'",
`script-src 'sha256-${base64(digest)}' 'wasm-unsafe-eval'`,
"style-src blob:",
"img-src blob:",
"img-src blob: data:",
"font-src blob:",
"media-src blob:",
"form-action 'none'",
Expand Down
Loading