Reference

Content Security Policy

The sources Rough needs when your app sets a Content Security Policy.

AI generated The content on this page was written using large language models.

You only need this page if your app sends a Content-Security-Policy header or meta tag. Many apps only turn one on in production, so Rough can work in development and then break when you deploy. If the Builder or your Features stop loading in production and the browser console shows Content-Security-Policy errors, this is the fix.

The policy

Add the Rough sources to the directives your policy already has, and keep your existing sources, including any your framework needs for its own scripts. Only add a directive if your policy doesn't have it yet. For a page that loads everything else from its own origin, the full policy looks like this:

default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
font-src 'self' data:;
img-src 'self' data: blob: https://cdn.rough.app;
connect-src 'self' https://gateway.rough.app wss://gateway.rough.app https://tus.rough.app;
frame-src https://plugin.rough.app

If you already have a policy, these are the sources to add:

connect-src https://gateway.rough.app wss://gateway.rough.app https://tus.rough.app
frame-src https://plugin.rough.app
img-src data: blob: https://cdn.rough.app
font-src data:
style-src 'unsafe-inline'

A header value has to fit on one line, so as a header the full policy looks like this:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob: https://cdn.rough.app; connect-src 'self' https://gateway.rough.app wss://gateway.rough.app https://tus.rough.app; frame-src https://plugin.rough.app

If you set the policy in your HTML instead, put the meta tag at the top of <head>, before any scripts. Line breaks are fine inside the meta tag:

<meta
  http-equiv="Content-Security-Policy"
  content="
    default-src 'self';
    script-src 'self';
    style-src 'self' 'unsafe-inline';
    font-src 'self' data:;
    img-src 'self' data: blob: https://cdn.rough.app;
    connect-src 'self' https://gateway.rough.app wss://gateway.rough.app https://tus.rough.app;
    frame-src https://plugin.rough.app
  "
>

What each source is for

Directive Source Why Rough needs it
connect-src https://gateway.rough.app Signs users in, syncs Features and streams Builder chat.
connect-src wss://gateway.rough.app Tells the SDK when Features change.
connect-src https://tus.rough.app Uploads images that users attach in the Builder.
frame-src https://plugin.rough.app Each Feature runs in an iframe served from this origin.
img-src https://cdn.rough.app Shows uploaded images in the Builder.
img-src blob: Previews an image before it finishes uploading.
img-src data: Draws small icons in the SDK's styles.
font-src data: Loads the SDK's fonts, which are bundled into its code.
style-src 'unsafe-inline' Adds the SDK's styles with <style> elements in its shadow roots.

You don't need to change script-src. The SDK is bundled with your own code, doesn't load scripts from Rough and doesn't use eval.

Some browsers block the live update connection if you only list https://gateway.rough.app, so include the wss:// origin too. Without it, Features still load, but changes such as a new publication can take up to a minute to appear.

Styles and nonces

Browsers ignore 'unsafe-inline' when the same directive also has a nonce or a hash. If your style-src uses a nonce, as the Next.js nonce example does, the SDK's styles are blocked and the Builder shows up unstyled. Either remove the nonce from style-src, or add a separate style-src-elem directive without a nonce:

style-src-elem 'self' 'unsafe-inline'

This also relaxes the rule for your own <style> elements. The SDK only adds <style> elements, not inline style attributes, so style-src-elem is enough if you want to keep style-src strict.

The SDK can't use a nonce for its styles yet. If your policy has to keep a nonce on every <style> element, let us know.

Zod's eval check

With a strict script-src, your browser console may show one violation for 'unsafe-eval'. It comes from Zod 4, which checks whether it can use new Function to speed up validation. When it can't, Zod uses its normal validation instead, so nothing breaks. Don't add 'unsafe-eval' to remove it.

To stop the check, turn off Zod's JIT before anything creates a schema. Put the call in its own module and import that module first in your browser entry file:

zod-config.ts
import { z } from 'zod'

z.config({ jitless: true })

If you change the SDK's URLs

If you pass baseUrl to startRoughClient, use its origin in connect-src instead of gateway.rough.app, once with https:// and once with wss://. If you pass mediaBaseUrl, use its origin in img-src instead of cdn.rough.app. Most apps don't set either option.

What your policy doesn't cover

Features run in an iframe on Rough's origin, so your policy doesn't apply inside them. You don't need to allow the scripts a Feature loads or the requests it makes. Your capability implementations run in your page, so they follow your policy like the rest of your code.

Where to set it

In SvelteKit, the policy usually lives in kit.csp.directives in svelte.config.js. SvelteKit adds the quotes around keywords for you, so write self and unsafe-inline without the inner quotes. Merge these lists with the ones you already have:

svelte.config.js
const config = {
  kit: {
    csp: {
      directives: {
        'connect-src': [
          'self',
          'https://gateway.rough.app',
          'wss://gateway.rough.app',
          'https://tus.rough.app',
        ],
        'frame-src': ['https://plugin.rough.app'],
        'img-src': ['self', 'data:', 'blob:', 'https://cdn.rough.app'],
        'font-src': ['self', 'data:'],
        'style-src': ['self', 'unsafe-inline'],
      },
    },
  },
}

export default config

In Next.js, the policy is usually set with headers() in next.config, or in middleware when you generate nonces. Add the sources there, and check style-src for a nonce as described above.

On other servers and CDNs, add the sources wherever you already set the Content-Security-Policy header.

Check it before you ship

To try a change without breaking anything, send the same policy as Content-Security-Policy-Report-Only first. The browser reports what it would have blocked in the console, and you can switch to the real header once the reports stop.

Then open the Builder, attach an image and load a published Feature with the console open. You shouldn't see any Content-Security-Policy errors.