> ## 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

> Learn which Content-Security-Policy directives let the ShieldLabs snippet 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 a module from a CDN, pulls in a dependency, and POSTs 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 https://cdn.jsdelivr.net;
connect-src 'self' https://rest.shieldlabs.ai https://webrtc.shieldlabs.ai stun:ice.shieldlabs.ai:3478;
```

## 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.                                        |
| `script-src`  | `https://cdn.jsdelivr.net`     | A required runtime dependency the snippet imports from jsDelivr. Omit this host and the snippet cannot finish loading. |
| `connect-src` | `https://rest.shieldlabs.ai`   | The snippet POSTs the collected signal snapshot here. This is the main data endpoint.                                  |
| `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.                                                                    |

<Note>
  `https://cdn.jsdelivr.net` belongs in `script-src` because that dependency is fetched as a module, not in `connect-src`. The three `shieldlabs.ai` data endpoints belong in `connect-src` because the snippet connects to them to send data.
</Note>

<Note>
  The snippet uses no `eval` and no `new Function`. It loads code by dynamic `import()` only, so `'unsafe-eval'` is never required, even under a strict policy.
</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, 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 null;
    }
    ```

    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 https://cdn.jsdelivr.net;
    connect-src 'self' https://rest.shieldlabs.ai https://webrtc.shieldlabs.ai stun:ice.shieldlabs.ai:3478;
    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 https://cdn.jsdelivr.net;
    connect-src 'self' https://rest.shieldlabs.ai https://webrtc.shieldlabs.ai stun:ice.shieldlabs.ai:3478;
  `;

  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 https://cdn.jsdelivr.net;
      connect-src 'self' https://rest.shieldlabs.ai https://webrtc.shieldlabs.ai stun:ice.shieldlabs.ai:3478;
    "
  />
  ```
</CodeGroup>

## A very strict CSP can skip the local-network check

The snippet also runs a local-network check that feeds the [Risk Score](/features/risk-scoring). A very strict `connect-src` that blocks the local-network connection stops this one check from running.

<Note>
  This is acceptable degradation. The main snapshot still posts, identification still works, and the visitor is still scored. When the local-network check cannot run, it is recorded as "not checked", which carries a small penalty, so the Score can shift slightly upward rather than down. It does not lower the Score. ShieldLabs only surfaces the anonymity signals and the Risk Score. Your own code still owns whether to allow, challenge, review, or block.
</Note>

## 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 dev tools Console.
  </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, the server scores the visit 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 score and signals arrive after the snapshot posts.
