Reference
Content Security Policy
The sources Rough needs when your app sets a Content Security Policy.
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:
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:
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.