# What an app page is allowed to load

> Every app page is served with a Content-Security-Policy the browser enforces. Scripts, styles, fonts, workers and data connections must come from the app's own origin; images may additionally come from any `https:` address. A control that tries to reach a third-party origin is refused by the browser, silently — so a vendored library must ship the parts it needs rather than fetch them at run time.

<!-- id: ui-content-security-policy · area: ui · stability: preview · html: https://osysharp.com/reference/ui/content-security-policy/ -->

## Summary        {#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](https://osysharp.com/reference/ui/control-chunks/), which exists so a library can ship its
own parts, including a worker and a `.wasm` sibling, and still be served from your app. WebAssembly compiled from
something your app served is allowed; a module fetched from someone else's origin never gets that far.

## Description    {#description}

### What the policy allows   {#allowed}

| 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` |
| **WebAssembly** | may be compiled, from a module the rules above let you fetch | a control may ship a `.wasm` chunk, and a browser refuses to compile one unless the policy says so. It permits compiling a module and nothing else — no string ever becomes JavaScript |
| **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** | the app's own pages, in either direction | a page may frame another page of the same app, and no other site may frame yours — which is what stops clickjacking. A page declared `[Embeddable]` is the one exception (below) |

Plugins (`object`), and a `<base>` element that could re-point every relative URL on the page, are refused outright.

Companion headers ride along on **every** response, a JSON answer and a 404 as much as a page: `nosniff`, so a browser
never guesses a type the server did not send; `X-Frame-Options: SAMEORIGIN` on anything whose own policy does not say
who may frame it; and a referrer 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.

### A page other sites may frame: `[Embeddable]`   {#embeddable}

A widget meant to sit on somebody else's site — a booking box, a status badge — says so on its component:

```osy title="a page any site may frame, one only partners may, and one nobody may" test app=ui-csp-embeddable
[Route("/")]
component Home() { render { Text("Welcome"); } }                  // framed by no other site — the default

[Route("/embed/{slug}")]
[Embeddable]
component BookingWidget(string slug) { render { Text("Book a table at " + slug); } }   // any site may frame it

[Route("/partner")]
[Embeddable("https://harbour.example", "https://*.ports.example")]
component PartnerDesk() { render { Text("Arrivals"); } }           // only these sites (and the app itself)
```

The page is then served with `frame-ancestors *`, or `frame-ancestors 'self' https://harbour.example …`. Every other
page keeps refusing. Each argument must be an origin — `https://` (or `http://`), a host that may start with `*.`, an
optional port, nothing after it; anything else is refused at compile, because a value carrying `;` would add a
directive of its own.

⚠ A framed page is the page — with the visitor's session if they are signed in to your app in that browser. Declare it
on pages that act only on what they show (`[AllowAnonymous]` pages, read-mostly views), not on a page with a
one-click destructive action.

### The failure is SILENT — this is the part worth remembering   {#silent-failure}

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:

```text
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   {#controls}

**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](https://osysharp.com/reference/ui/control-chunks/).

**WebAssembly vendors cleanly too, and this is where people expect trouble.** A `.wasm` is an ordinary chunk: it is
served from your app with the right type, and the policy admits compiling it. The one thing to watch is that
Emscripten-built libraries do not embed their module — they *ask* for its address at run time, and the default answer
is usually a CDN, which will be refused. Point that lookup at your own chunk instead. `Osysharp.BarcodeScanner`
([The barcode kit — a QR and barcode scanner you opt into](https://osysharp.com/reference/ui/barcode-kit/)) is the worked example: a 1.07 MB ZXing decoder reached through `host.chunkUrl("Wasm")`.

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.** `![](https://…)` 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       {#see-also}
- [chunks — assets a control loads on demand](https://osysharp.com/reference/ui/control-chunks/) — 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)](https://osysharp.com/reference/ui/controls/) — declaring and mounting a control
- [The markdown editor kit — a rich editor you opt into](https://osysharp.com/reference/ui/markdown-editor-kit/) — a worked example of a vendored, chunked control
