Summary#
An app page is not an open document. It is served with a Content-Security-Policy, and the browser — not the platform — enforces it. The shape is same-origin by default: the client, every control bundle, every chunk, every font and every data connection comes from the app's own address.
Two deliberate widenings make ordinary content work. Images may come from any https: address, so a document that
embeds a remote picture renders. And inline styles are allowed, because that is how the renderer and every
editor-style control position things.
The rule with teeth for a control author is the one about fetching: a control may not load code, data or a font
from a third-party origin. Vendor what you need — see chunks — assets a control loads on demand, which exists so a library can ship its
own parts, including a worker and a .wasm sibling, and still be served from your app.
Description#
What the policy allows#
| What | Allowed from | Why it is drawn there |
|---|---|---|
| Scripts | the app's own origin, plus one hashed inline bootstrap | the client, control bundles and chunks are all served by your app. There is no unsafe-inline and no unsafe-eval |
| Styles | the app's origin, and inline | the renderer paints style attributes, and controls position themselves with them continuously |
| Images | the app's origin, any https: address, and data: | a document that embeds a remote image is ordinary content, and stylesheets legitimately draw small icons as data: SVG |
| Fonts | the app's origin, and data: | your app's web fonts are served by your app; a chunk's stylesheet may inline a face |
| Connections (fetch, WebSocket) | the app's origin | the data channel and live updates are all your app's own address |
| Workers | the app's origin | a package chunk may start one; it is served from your app like the rest of the package |
| Frames | nothing, in either direction | no page embeds another, and no page may be embedded — which is also what stops clickjacking |
Plugins (object), and a <base> element that could re-point every relative URL on the page, are refused outright.
Two companion headers ride along: responses are marked nosniff, and the referrer sent to another site is trimmed to
your origin — so following a link out of a document does not hand the other site the record id in your page's address.
The failure is SILENT — this is the part worth remembering#
A refused subresource does not raise an error your code can catch. The browser simply does not fetch it, writes a line to the console, and carries on. A feature that lazily loads something therefore does not break — it quietly does nothing, which looks like a bug anywhere except where it is.
So when a control works in isolation and does nothing in an app, open the browser console first. A CSP refusal names the directive and the address it blocked:
Refused to load the script 'https://cdn.example.com/lib.js' because it violates
the following Content Security Policy directive: "script-src 'self' 'sha256-…'".What this means when you ship a control#
Vendor, do not fetch. A library that hard-codes a CDN address for its own code will be refused. The answer is to
ship it as part of your control, which is what a chunk package is for: a directory served under a real base, so the
library's own relative imports, new Worker(new URL(…)) and .wasm siblings all resolve exactly as they would on a
static host. See chunks — assets a control loads on demand.
That covers more than it sounds like it should — a diagram engine that lazy-loads a renderer per diagram type, or a speech model's WASM runtime, both vendor cleanly. What it does not cover is a library that must reach its own origin at run time, such as one fetching multi-hundred-megabyte model weights from a CDN. There is no way to declare that today; the page's policy refuses it, and that is the current answer rather than an oversight.
Remote images are fine.  in a document, or an image whose address is data, renders normally. Only
http: addresses are refused, and those would be blocked as mixed content on a secure page anyway.
Inline styles are fine. Setting a style attribute from a control works, as does anything the renderer paints.
See also#
- chunks — assets a control loads on demand — how to ship a library's own parts so it is served from your app rather than fetched
- control — foreign UI controls (charts, grids, maps) — declaring and mounting a control
- The markdown editor kit — a rich editor you opt into — a worked example of a vendored, chunked control