---
title: Content Security Policy
description: Write a Content Security Policy for a Svelte app using c15t, with
  the hosts to allow, the provider's nonce option and what c15t injects.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## 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 does|Directive|Allow|
|--|--|--|
|Requests the policy from `/init` and saves choices to `/subjects`|`connect-src`|Your backend's origin. `'self'` covers a same-origin route such as `/api/c15t`.|
|Adds a `<script>` element for each allowed entry in `scripts`|`script-src`|Each vendor's script host, or the provider's `nonce`.|
|Lets vendor scripts load more scripts, pixels and requests|`script-src`, `img-src`, `connect-src`|The hosts each vendor documents.|
|Sets `src` on gated iframes, or mounts `ConsentGate` children|`frame-src`|Each embed's host, such as `https://www.youtube-nocookie.com`.|
|Adds its rules to `<head>` as `<style>` elements|`style-src`|The provider's `nonce`. With `styles={false}` and an imported `@c15t/svelte/styles.css`, `'self'`.|
|Fetches the Global Vendor List under an IAB policy|`connect-src`|The 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
<!-- 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.
