From f5170c83cdf139ae2c4b6d16e7a9640de694f4e1 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Thu, 20 Aug 2026 11:19:02 +0200 Subject: [PATCH 1/3] docs(wasm): say what csp the rendered output needs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Self-contained" is true of the markup but not of what a page may load: a frame inherits the embedding page's policy, so the embedder's CSP governs the rendered document, and nothing said what that has to allow. The failures are quiet — under `font-src 'self'` a pdf's `data:` fonts are blocked, and because they carry the code points the text is painted with, every glyph comes out as a replacement box instead of falling back to a system face. Measured across an odt, ods, docx and pdf rendered through the package: `font-src data:`, `img-src data:`, `style-src 'unsafe-inline'` (blocks and, more to the point, hundreds of `style` attributes, which a nonce cannot cover) and `script-src 'unsafe-inline'`. Nothing is fetched from another origin. Closes #710 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XDs5aK3ZGSZsEvqUUwBBXU --- CHANGELOG.md | 4 ++++ wasm/README.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cca131c..90e4ec64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ The release run heads these entries with the version and opens a fresh the wasm module is linked with `-sDYNAMIC_EXECUTION=0`, so embind builds its invokers without `new Function`. `script-src 'self' 'wasm-unsafe-eval'` is now enough. +- `wasm/README.md` says what Content-Security-Policy the rendered output needs, + and what each directive is for. An embedder inherits its own policy into the + frame, and the failures are quiet — a pdf whose `data:` fonts are blocked + renders as tofu rather than falling back. ## v6.9.0 - 2026-08-18 diff --git a/wasm/README.md b/wasm/README.md index f5871123..aace1b4f 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -47,7 +47,10 @@ try { so it needs nothing fetched alongside it. A `blob:` iframe keeps the same origin, so the page can still reach `iframe.contentWindow.odr` to drive `search()`, `searchNext()` and `generateDiff()`, exactly as the Android and iOS -apps do from their WebViews. +apps do from their WebViews. Inline is not the same as unconditional: the frame +inherits the embedding page's Content-Security-Policy, so a page that ships one +has to allow what the document carries — see +[Content-Security-Policy](#content-security-policy). Multi-page formats render one view at a time: @@ -86,6 +89,46 @@ where `Symbol.dispose` is supported. - Rendering is synchronous and a large PDF takes seconds, so run the module in a Web Worker. Pass `doc.handle` across `postMessage`, never the `Document`. +## Content-Security-Policy + +The rendered html is self-contained, but a frame created from an embedding page +inherits that page's policy — so the *embedder's* CSP decides what the document +may load, and the failures are quiet. Measured across an odt, ods, docx and pdf +rendered by this package: + +| Directive | What in the output needs it | Seen in | +|---|---|---| +| `font-src data:` | embedded subset fonts | pdf (7 of 8 `@font-face`) | +| `img-src data:` | embedded images | odt, docx, standalone images | +| `style-src 'unsafe-inline'` | the document's own `