# Content security policy

> The Content-Security-Policy directives a page needs to run an embedded Mindset agent, with a ready policy to copy.

After this page your site's content security policy (CSP) lets the Mindset agent load and run, and still blocks everything else you mean it to block.

If your site sends no `Content-Security-Policy` header or meta tag, you can skip this page.

## What the agent needs

Everything the SDK fetches comes from one place: your Mindset host, the address on the agent's **Embed** tab. The examples use `https://eu.mindset.ai`. Replace it with yours.

| Directive | Allow | Why |
|---|---|---|
| `script-src` | Your Mindset host | The page loads `/sdk/mindset-agent.js` (or imports `/sdk/mindset-agent-uiless.js`) from it. The element also loads `/sdk/mindset-agent-charts.js` from the same host the first time it draws a chart |
| `connect-src` | Your Mindset host | Every turn, tool call and session renewal is a `fetch` to it. Replies stream over that `fetch`, so there is no WebSocket and no `wss:` to allow |
| `font-src` | Your Mindset host | The element loads the Inter typeface from `/fonts/` on the host. Blocked, it falls back to the system font |
| `img-src` | The hosts your agent's images come from | An agent can show images from anywhere its content points (markdown images in replies, image widgets). Allow the hosts your agents use, or `https:` |
| `style-src` | Nothing, in current browsers | See [styles](#styles) below |

Your own backend, where `getSession` fetches the session, needs to be in `connect-src` too. `'self'` covers it when it's on the same origin as the page.

The SDK uses no `eval` and no `new Function`, so you don't need `'unsafe-eval'`.

## A ready policy

```
Content-Security-Policy:
  script-src  'self' https://eu.mindset.ai;
  connect-src 'self' https://eu.mindset.ai;
  font-src    https://eu.mindset.ai;
  img-src     'self' data: https:;
```

Send it as one header line. The line breaks are for reading. Narrow `img-src` to the image hosts your agents use. Keep any other directives your site already has, such as `default-src`, alongside these.

## A nonce-based policy

If your policy allows scripts by nonce instead of by host, put the nonce on the Mindset script tag:

```html
<script nonce="RANDOM-PER-RESPONSE" src="https://eu.mindset.ai/sdk/mindset-agent.js"></script>
```

```
Content-Security-Policy:
  script-src  'nonce-RANDOM-PER-RESPONSE' 'strict-dynamic';
  connect-src 'self' https://eu.mindset.ai;
  font-src    https://eu.mindset.ai;
  img-src     'self' data: https:;
```

The element copies that nonce onto the chart script it adds later, so charts load too.

The nonce is picked up only when the bundle loads from a classic `<script src=".../mindset-agent.js">` tag, including one you add from code. Loaded any other way, the chart script carries no nonce. Then allow your Mindset host in `script-src`, or keep `'strict-dynamic'` so scripts your trusted scripts add are allowed.

## Styles

The element puts its stylesheet in its own shadow root as a constructed stylesheet. Current browsers don't apply `style-src` to constructed stylesheets, so the element needs no `'unsafe-inline'` and no style entry for the Mindset host.

Two things do add a `<style>` element, and a strict `style-src` blocks them:

- **Older Safari** (before 16.4) can't construct stylesheets, so the element falls back to a `<style>` element in its shadow root.
- **Dialogs, popovers and menus** inside the chat add a small `<style>` element to your page's `<head>` while they are open, to stop the page behind them scrolling.

Blocking these is cosmetic: the browser reports a CSP violation and that styling is lost. In current browsers that means the page behind an open dialog can still scroll. On older Safari the chat loses its stylesheet. If you need it to look right on older Safari, add `style-src 'self' 'unsafe-inline'`. Otherwise leave `style-src` strict.

## What Mindset sends back

You don't configure any of this, but it explains why the policy above is enough.

- **`/sdk/*` and `/fonts/*`** are served with `Access-Control-Allow-Origin: *` and `Cross-Origin-Resource-Policy: cross-origin`, so a page on any origin can load them.
- **The routes the agent runs on** answer cross-origin requests from any origin, without cookies. The session credential travels in an `Authorization` header, never a cookie.
- **The session create call** has no CORS headers at all, so a browser can't make it. It belongs on your server. See [Create a session for your users](https://docs4.mindset.ai/docs/sdk/create-a-session-for-your-users).

## What can go wrong

| What you see | Cause and fix |
|---|---|
| The console reports a blocked script from your Mindset host, and no panel appears | `script-src` doesn't allow the host, or the script tag lacks the nonce your policy requires |
| The panel appears but every message fails with `turn-failed` | `connect-src` doesn't allow the host. The console shows the blocked `fetch` |
| Charts say they couldn't be drawn | The chart script was blocked. Allow the host in `script-src`, or put your nonce on the main script tag |
| Text is in a system font | `font-src` doesn't allow the host |
| CSP violation reports mention `style-src` while a dialog is open | The scroll lock's `<style>` element. It's cosmetic. See [styles](#styles) |
| Images in replies are broken | `img-src` doesn't allow the host the image comes from |

## Related

- [Put an agent on your page](https://docs4.mindset.ai/docs/sdk/put-an-agent-on-your-page) for the basic setup.
- [Errors and resilience](https://docs4.mindset.ai/docs/sdk/errors-and-resilience) for every error the SDK raises.
