> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shieldlabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Content Security Policy

> The Content-Security-Policy directives the ShieldLabs snippet needs to load and report.

If your application sends a `Content-Security-Policy` header, the browser will block the ShieldLabs snippet unless you add its hosts to your allowlists. The snippet loads its modules from the ShieldLabs CDN and sends the collected signals to a few `shieldlabs.ai` endpoints. Each of those needs a directive.

If you have not added the snippet yet, start with [Add the snippet](/setup/snippet) and come back here once it loads.

## Required directives

Add these two directives to your existing policy. Merge the hosts into directives you already have rather than duplicating them.

```
script-src  'self' https://cdn.shieldlabs.ai;
connect-src 'self' https://rest.shieldlabs.ai wss://rest.shieldlabs.ai https://webrtc.shieldlabs.ai stun:ice.shieldlabs.ai:3478;
img-src     'self' https://rest.shieldlabs.ai;
```

## What each host is for

`script-src` covers the code that runs. `connect-src` covers where the snippet sends data.

| Directive | Host | Why it is needed |
| - | - | - |
| `script-src` | `https://cdn.shieldlabs.ai` | Serves the ShieldLabs snippet module (`snippet.js`) and its supporting modules. |
| `connect-src` | `https://rest.shieldlabs.ai` | The snippet POSTs the collected signal snapshot here. This is the main data endpoint. |
| `connect-src` | `wss://rest.shieldlabs.ai` | A ShieldLabs data endpoint the snippet connects to over WebSocket. |
| `connect-src` | `https://webrtc.shieldlabs.ai` | A ShieldLabs data endpoint the snippet connects to. |
| `connect-src` | `stun:ice.shieldlabs.ai:3478` | A ShieldLabs data endpoint the snippet connects to. |
| `img-src` | `https://rest.shieldlabs.ai` | The optional `<noscript><img>` beacon (`GET /noscript`) for JS-off clients and crawlers that follow image URLs. |

<Note>
  All ShieldLabs code loads from `https://cdn.shieldlabs.ai`, so `script-src` needs that one host. The `shieldlabs.ai` data endpoints belong in `connect-src` because the snippet connects to them to send data.
</Note>

<Note>
  The snippet loads code by dynamic `import()` only, so it runs under a policy without `'unsafe-eval'`.
</Note>

## Inline scripts: HTML method vs framework method

The two install methods from [Add the snippet](/setup/snippet) have different inline-script requirements.

<Tabs>
  <Tab title="HTML script tag (needs inline)">
    The HTML method runs an inline `<script type="module">` that calls `import()`:

    ```html theme={null}
    <script type="module">
      const mod = await import('https://cdn.shieldlabs.ai/snippet.js?publicKey=YOUR_PUBLIC_KEY');
      mod.checkAnonymous();
    </script>
    ```

    Because the bootstrap code is inline, a policy that forbids inline scripts will block it. You have two options:

    1. Move the bootstrap into an external module file you serve from `'self'` (no inline code), or
    2. Use the framework component method instead (next tab), which has no inline script at all.

    Avoid `'unsafe-inline'` if you can. The framework method removes the need for it entirely.
  </Tab>

  <Tab title="Framework component (no inline)">
    In React, Next.js, Vue, Angular, Svelte, or Preact the `import()` lives inside a component or service in your application code, which is already covered by `script-src 'self'` (or your bundler host). There is no inline script, so you do not need `'unsafe-inline'`:

    ```jsx theme={null}
    import { useEffect } from "react";

    export function ShieldLabsTracker({ publicKey }) {
      useEffect(() => {
        let cancelled = false;
        (async () => {
          const mod = await import(
            `https://cdn.shieldlabs.ai/snippet.js?publicKey=${publicKey}`
          );
          if (!cancelled) mod.checkAnonymous();
        })();
        return () => { cancelled = true; };
      }, [publicKey]);

      return (
        <noscript>
          <img
            src={`https://rest.shieldlabs.ai/noscript?publicKey=${publicKey}`}
            width={1}
            height={1}
            alt=""
          />
        </noscript>
      );
    }
    ```

    In Next.js, use the client component from the Next.js tab on [Add the snippet](/setup/snippet#framework-integrations): it adds `'use client'` and the `webpackIgnore` comment.

    This is the cleanest fit for a strict CSP. The full set of framework examples is on [Add the snippet](/setup/snippet).
  </Tab>
</Tabs>

## Full-header examples

Drop these into your stack and adjust the surrounding directives to match your app. Only the two ShieldLabs directives are required.

<CodeGroup>
  ```nginx Nginx theme={null}
  add_header Content-Security-Policy "
    default-src 'self';
    script-src  'self' https://cdn.shieldlabs.ai;
    connect-src 'self' https://rest.shieldlabs.ai wss://rest.shieldlabs.ai https://webrtc.shieldlabs.ai stun:ice.shieldlabs.ai:3478;
    img-src     'self' https://rest.shieldlabs.ai;
    style-src   'self' 'unsafe-inline';
    base-uri    'self';
    frame-ancestors 'none'
  " always;
  ```

  ```js Next.js (next.config.js) theme={null}
  const csp = `
    default-src 'self';
    script-src  'self' https://cdn.shieldlabs.ai;
    connect-src 'self' https://rest.shieldlabs.ai wss://rest.shieldlabs.ai https://webrtc.shieldlabs.ai stun:ice.shieldlabs.ai:3478;
    img-src     'self' https://rest.shieldlabs.ai;
  `;

  module.exports = {
    async headers() {
      return [
        {
          source: "/(.*)",
          headers: [
            { key: "Content-Security-Policy", value: csp.replace(/\n/g, " ").trim() },
          ],
        },
      ];
    },
  };
  ```

  ```html HTML meta tag theme={null}
  <meta
    http-equiv="Content-Security-Policy"
    content="
      default-src 'self';
      script-src  'self' https://cdn.shieldlabs.ai;
      connect-src 'self' https://rest.shieldlabs.ai wss://rest.shieldlabs.ai https://webrtc.shieldlabs.ai stun:ice.shieldlabs.ai:3478;
      img-src     'self' https://rest.shieldlabs.ai;
    "
  />
  ```
</CodeGroup>

## If a connection is blocked

If `connect-src` omits `https://rest.shieldlabs.ai`, the snapshot cannot post and no identification is recorded. If it omits `https://webrtc.shieldlabs.ai`, the identification still posts and is scored, but the network check cannot complete, and those identifications can carry the `stun_not_checked` risk signal (weight 30), which on its own puts a real user in the Suspicious band. Keep every host above in your policy.

## Verify it works

After deploying your policy:

<Steps>
  <Step title="Load a page with the snippet">
    Open a page where the snippet runs and open your browser's developer tools.
  </Step>

  <Step title="Check for CSP violations">
    A blocked host shows a `Refused to load` or `Refused to connect` error naming the directive and the host. If you see one, add that host to the directive it names.
  </Step>

  <Step title="Confirm the snapshot posted">
    In the Network tab, confirm a request to `rest.shieldlabs.ai` succeeded. Once it does, ShieldLabs scores the identification and delivers your [webhook](/setup/webhooks).
  </Step>
</Steps>

## Related

* [Add the snippet](/setup/snippet): the HTML and framework install methods these directives support.
* [API keys](/setup/keys): the Public Key that goes in the snippet URL.
* [Webhooks](/setup/webhooks): where the Risk Score and risk signals arrive after the snapshot posts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.