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

# Content Security Policy for Flows custom apps

> Understand the Content Security Policy (CSP) for Flows custom apps and how to allow trusted network sources in manifest.json.

When you deploy a Flows custom app, the platform applies a strict [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) — a browser security feature that controls which external resources the app is allowed to load. CSP limits where scripts, styles, images, fonts, frames, workers, media, and network requests can come from.

Most apps only need to change CSP when they call an external API, load map tiles, embed a frame, use a CDN, or add a dependency that creates workers or evaluates code at runtime. Add trusted sources in `permissions.network` in `manifest.json`.

<Tip>
  Run the app locally first. The [Flows Vite plugin](/cdf/flows/reference/api/vite) applies CSP during development and logs a manifest snippet when the browser blocks a request.
</Tip>

## How it works

Use `manifest.json` to add trusted sources to supported fetch directives:

```json theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
{
  "manifestVersion": 1,
  "permissions": {
    "network": [
      {
        "sources": ["https://tiles.example.test"],
        "directives": ["img-src", "connect-src"]
      },
      {
        "sources": ["https://api.partner.test"]
      }
    ]
  }
}
```

If `directives` is omitted, the source is added to `connect-src`.

Apps can't remove baseline restrictions, such as `object-src 'none'`, or widen document-level directives, such as `default-src` and `frame-ancestors`. Every Flows app starts with the production baseline.

## Debug CSP violations

Run the app locally:

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
npm run dev
```

When the browser blocks a request, the Vite plugin logs a `manifest.json` snippet to the browser console (DevTools → Console tab):

```text wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
Flows Apps [linked-app-sdk v2] blocked connect-src -> https://tiles.example.test
Update your manifest.json with the snippet below:
{
  "manifestVersion": 1,
  "permissions": {
    "network": [
      {
        "sources": ["https://tiles.example.test"],
        "directives": ["connect-src"]
      }
    ]
  }
}
```

Paste the rule into `manifest.json`. The dev server picks up the change without a restart.

## Production baseline

Hosted production apps use this CSP baseline:

```text wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
frame-ancestors https://*.fusion.cognite.com https://*.fusion.cogniteapp.com
base-uri 'self'
connect-src 'self' https://*.cognitedata.com wss://*.cognitedata.com
default-src 'self'
font-src 'self' data: https://fonts.gstatic.com
form-action 'self'
frame-src 'self'
img-src 'self' data: blob:
media-src 'self'
object-src 'none'
script-src 'self'
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com
worker-src 'self' blob:
```

Important details:

* CDF API and WebSocket endpoints are already allowed in `connect-src`.
* Production `script-src` allows `'unsafe-inline'` to be added via `manifest.json`, but this is strongly discouraged. Use [hash sources](#inline-scripts-in-script-src) instead.
* `eval` and `new Function(...)` are always blocked in production, regardless of `manifest.json`.
* `style-src` includes `'unsafe-inline'` because many UI libraries inject style tags.
* Aura font sources are already allowed.
* `frame-ancestors` is controlled by App Hosting and can't be changed from `manifest.json`.

## Development differences

Development uses the same baseline with a few additions for Vite and local framing:

* `script-src` also includes `'unsafe-inline'` and `'unsafe-eval'` for Vite HMR and React Refresh.
* `http://localhost`, `http://127.0.0.1`, `ws://localhost`, and `ws://127.0.0.1` sources are accepted during development.
* Local and preview CDF origins can frame the app.

These additions are removed in production. Dependencies that rely on development-only script relaxations will fail after deployment.

## Choose the right directive

Use the browser CSP error to identify the blocked directive, then add the narrowest source that covers it.

| Requirement                                 | Directive                            |
| ------------------------------------------- | ------------------------------------ |
| External HTTPS API                          | `connect-src`                        |
| WebSocket                                   | `connect-src` with a `wss://` source |
| Map tiles, images, SVGs, or canvas textures | `img-src`                            |
| Web fonts                                   | `font-src`                           |
| CDN stylesheet                              | `style-src`                          |
| Embedded iframe                             | `frame-src`                          |
| Audio or video                              | `media-src`                          |
| Worker script from another origin           | `worker-src`                         |
| JavaScript file from another origin         | `script-src`                         |

<Warning>
  Use `script-src` only for trusted script files. It can't approve `eval`, `new Function(...)`, or inline script tags in production.
</Warning>

## Manifest reference

Each `permissions.network` entry has this shape:

```ts theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
type NetworkRule = {
  sources: string[];
  directives?: CspFetchDirective[];
};
```

If `directives` is omitted, the source is added to `connect-src`. The valid values for `CspFetchDirective` are the eight directives in [Extensible directives](#extensible-directives).

### Extensible directives

Only these fetch directives can be extended:

```text wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
connect-src, font-src, frame-src, img-src, media-src, script-src, style-src, worker-src
```

Rules that only target unsupported directives are dropped. Unsupported examples include `default-src`, `frame-ancestors`, `object-src`, `base-uri`, and `form-action`.

### Source formats

Production accepts:

* HTTPS sources, for example `https://api.example.test` or `https://api.example.test:8443/v1`
* Secure WebSocket sources, for example `wss://stream.example.test`
* Host wildcards, for example `https://*.example.test`

Development also accepts local HTTP and WebSocket sources:

* `http://localhost:3000`
* `http://127.0.0.1:3000`
* `ws://localhost:5173`
* `ws://127.0.0.1:5173`

For `script-src`, production also accepts:

* `'wasm-unsafe-eval'`
* `'strict-dynamic'`
* Hash sources such as `'sha256-...'`, `'sha384-...'`, and `'sha512-...'`

The policy rejects sources with whitespace, control characters, semicolons, stray quotes, `javascript:` URLs, `data:` URLs, and broad wildcards, such as `https://*`.

Nonces are not supported — the Flows custom apps service does not generate per-request nonces.

<Warning>
  `manifest.json` can't add `'unsafe-eval'` to production `script-src`. Adding `'unsafe-inline'` is technically allowed but strongly discouraged — use a hash source to allowlist only the specific script you need, or move the code to an external file bundled by Vite. See [inline scripts](#inline-scripts-in-script-src).
</Warning>

## Examples

### External API

```json theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
{
  "manifestVersion": 1,
  "permissions": {
    "network": [
      {
        "sources": ["https://api.partner.test"]
      }
    ]
  }
}
```

Because `directives` is omitted, the source is added to `connect-src`.

### Map tiles

```json theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
{
  "manifestVersion": 1,
  "permissions": {
    "network": [
      {
        "sources": ["https://tiles.example.test"],
        "directives": ["img-src", "connect-src"]
      }
    ]
  }
}
```

Some map libraries fetch tile metadata with `fetch()` and load tile images through `<img>` or canvas, so they need both directives.

### CDN stylesheet

```json theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
{
  "manifestVersion": 1,
  "permissions": {
    "network": [
      {
        "sources": ["https://cdn.example.test"],
        "directives": ["style-src"]
      }
    ]
  }
}
```

### WebAssembly

```json theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
{
  "manifestVersion": 1,
  "permissions": {
    "network": [
      {
        "sources": ["'wasm-unsafe-eval'"],
        "directives": ["script-src"]
      }
    ]
  }
}
```

Add this only when a library needs WebAssembly compilation at runtime.

## Common library issues

Some libraries use runtime patterns that production CSP blocks.

### Libraries that use `eval` or `new Function`

Templating, expression, charting, and schema libraries sometimes compile strings into functions at runtime. That requires `'unsafe-eval'` in `script-src`, which production apps don't allow.

Use a build-time compiler, a precompiled distribution, or a different dependency.

### `react-pdf` and `pdfjs-dist`

Configure the PDF worker as a same-origin asset bundled by Vite:

```ts theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
import { pdfjs } from "react-pdf";

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.min.mjs",
  import.meta.url
).toString();
```

The URL resolves to the app's own origin, which is covered by `'self'` in `worker-src`.

### `troika-three-text` and `reagraph`

`troika-three-text`, used by `reagraph`, probes worker support with a blob worker and can call `importScripts()` with generated URLs. If that path is blocked, force troika onto the main thread before any `Text` instances or reagraph renders are created:

```ts theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
import { configureTextBuilder } from 'troika-three-text';

configureTextBuilder({ useWorker: false });
```

### Inline scripts in `script-src`

Adding `'unsafe-inline'` to `script-src` allows any inline `<script>` tag in the app to run, which removes a meaningful layer of XSS protection. Prefer one of these alternatives:

**Hash source** — allowlists one specific inline script by its content hash. Nonces are not supported; hashes are the equivalent approach here.

1. Compute the SHA-256 hash of the script body (no `<script>` tags, whitespace-exact):

   ```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
   echo -n 'your inline script content here' | openssl dgst -sha256 -binary | base64
   ```

2. Add the result to `manifest.json`:

   ```json theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
   {
     "manifestVersion": 1,
     "permissions": {
       "network": [
         {
           "sources": ["'sha256-<base64-hash>'"],
           "directives": ["script-src"]
         }
       ]
     }
   }
   ```

**External file** — move the inline code into a `.ts` or `.js` file imported by the bundle. The bundler includes it as a same-origin script, which is covered by `'self'` and requires no manifest change.

If neither option is feasible and the inline script comes from a third-party library, check whether the library offers a CSP-compatible build or configuration option before falling back to `'unsafe-inline'`.

## Troubleshooting

### It works locally but fails in production

Check for one of these causes:

* The source is `localhost`, `127.0.0.1`, `http://...`, or `ws://...`. Those are development-only.
* A dependency relies on development-only `script-src` relaxations, such as `'unsafe-eval'`.
* The deployed bundle doesn't include the `manifest.json` change. Redeploy the app after updating the manifest.
* The blocked request happens inside a worker, where the dev warning may not produce a manifest snippet. Use the browser console's CSP violation details to identify the directive and source.

### The manifest rule is ignored

Check that:

* `manifestVersion` is `1`.
* `sources` isn't empty.
* `directives` contains at least one supported fetch directive.
* The source uses an allowed scheme and doesn't contain whitespace, semicolons, control characters, or stray quotes.

### A library asks for `'unsafe-inline'` in `script-src`

This can be added via `manifest.json` but is strongly discouraged — it bypasses script integrity for every inline script in the app. Use a hash source to allowlist only the specific script, or move the code to an external file. See [inline scripts](#inline-scripts-in-script-src).

### A library asks for `'unsafe-eval'` in `script-src`

`'unsafe-eval'` can't be added to production `script-src`. Use a CSP-safe build, precompile the dynamic code, or replace the library.

## Related

* [Vite plugin API](/cdf/flows/reference/api/vite) — `fusionOpenPlugin` and development CSP.
* [Get started with Cognite Flows](/cdf/flows/guides/getting-started) — project scaffolding, including `manifest.json`.
* [Deploy your Flows app](/cdf/flows/guides/deploying) — how the manifest reaches App Hosting.
