Skip to main content

Svelte Advanced

Content Security Policy

Set the policy where you host the app

A Svelte app built with Vite has no server of its own, so the policy comes from your host or CDN as a Content-Security-Policy header, or from a <meta http-equiv="Content-Security-Policy"> tag in index.html. Test it against vite build and vite preview. The dev server injects CSS through <style> elements, so a strict policy breaks vite dev even when production works.

A static host serves the same file to everyone and cannot put a fresh nonce in each response, so allow each vendor's script host in script-src instead of relying on the provider's nonce.

In a Svelte app without server rendering, Svelte sets every style value through the DOM, which a policy does not block, so c15t needs no 'unsafe-inline' for style attributes.

c15t's components add their rules to <head> as <style> elements, and a style-src without 'unsafe-inline' blocks those. A static host has no nonce to give the provider, so set styles={false} on ConsentProvider and import @c15t/svelte/styles.css from your entry module. 'self' covers that stylesheet.

What c15t adds to the page

A Content Security Policy has to allow each thing c15t or your vendors put on the page:

c15t doesDirectiveAllow
Requests the policy from /init and saves choices to /subjectsconnect-srcYour backend's origin. 'self' covers a same-origin route such as /api/c15t.
Adds a <script> element for each allowed entry in scriptsscript-srcEach vendor's script host, or the provider's nonce.
Lets vendor scripts load more scripts, pixels and requestsscript-src, img-src, connect-srcThe hosts each vendor documents.
Sets src on gated iframes, or mounts ConsentGate childrenframe-srcEach embed's host, such as https://www.youtube-nocookie.com.
Adds its rules to <head> as <style> elementsstyle-srcThe provider's nonce. With styles={false} and an imported @c15t/svelte/styles.css, 'self'.
Fetches the Global Vendor List under an IAB policyconnect-srcThe vendor list host the policy names, when it is not your own origin.

The dev tools panel attaches a stylesheet to its shadow root. Keep the panel out of production builds, as its page describes, and a production policy does not need to allow it.

Stamp a nonce on vendor scripts

If your server creates a nonce for each response and puts it in the Content-Security-Policy header as 'nonce-<value>', pass the same value to the provider:

<!-- Svelte: src/App.svelte -->
<ConsentProvider mode={manifest()} {scripts} nonce={cspNonce}>

<!-- SvelteKit: src/routes/+layout.svelte -->
<ConsentRoot state={data.consent} {scripts} nonce={cspNonce}>

The script loader then sets that nonce on every <script> it adds, for both src and inline textContent scripts. A nonce on one scripts entry wins for that script. The components put it on the <style> elements they add. The provider reads nonce once, when it is created.

The nonce covers only the elements c15t creates. A script that a vendor's script loads by itself does not get it; allow that host in script-src, or add 'strict-dynamic' so scripts loaded by a trusted script are trusted too. With 'strict-dynamic', browsers ignore host allowlists in script-src, so every script on the page needs the nonce or a hash.

Verify the policy

Enforce the policy, not report-only, and open the console with site data cleared:

  1. The banner renders with its styles, and no Refused to message mentions a c15t element, a <style data-c15t-styles> element or @c15t/svelte/styles.css. Open the preference dialog and check again.
  2. Allow a category. Its vendor scripts load, and none are refused. A refused script from a vendor's own host means that host is missing from script-src.
  3. Save a choice. The request to /subjects succeeds; a connect-src violation means the backend's origin is missing.
  4. Allow the category of a gated embed. It loads; a frame-src violation names the host to add.