CSP Mistakes with Unleash Feature Flags
Table of Contents
Feature flags look harmless until CSP starts blocking them in production.
Unleash is a good example. The SDK itself is usually straightforward, but the moment you load a frontend client, bootstrap flags, poll an API, or stream updates over SSE or WebSockets, your CSP needs to be precise. Most breakages come from people treating Unleash like “just another script” when the real problem is usually connect-src.
I’ve seen teams waste hours tweaking script-src while the browser was yelling about a blocked fetch the whole time.
Here are the common mistakes I keep seeing with CSP for Unleash, plus the fixes that actually work.
Mistake 1: Only updating script-src
This is the classic one.
People add the Unleash SDK host to script-src, deploy, and still get broken flags. That’s because the SDK script is only one part of the flow. Once loaded, it still needs to talk to the Unleash API. That communication is controlled by connect-src, not script-src.
A common real-world CSP already shows how different directives split responsibility:
content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-OTJlOTg3NDctNWQwOS00ZmZlLTgzZmItNDQxOGMxYWI1NTdh' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com;
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com;
frame-src 'self' https://consentcdn.cookiebot.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
Notice how API calls and WebSockets are under connect-src. That’s exactly where Unleash usually belongs.
Broken policy
Content-Security-Policy:
default-src 'self';
script-src 'self' https://app.unleash-hosted.com;
Why it fails
The script may load, but calls like these will be blocked:
fetch()to frontend API endpointsEventSourcefor streaming updatesWebSocketif your setup uses it
Fix
Add the Unleash API origin to connect-src.
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN;
If your deployment uses WebSockets:
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN wss://YOUR-UNLEASH-ORIGIN;
If you need ready-made policy patterns, the examples at https://csp-examples.com are useful as a starting point.
Mistake 2: Putting Unleash under default-src and assuming that’s enough
Technically, default-src acts as a fallback for directives you didn’t define. In practice, most production CSPs already define script-src and connect-src, so default-src won’t save you.
I see policies like this:
Content-Security-Policy:
default-src 'self' https://YOUR-UNLEASH-ORIGIN;
script-src 'self';
connect-src 'self';
The author thinks Unleash is allowed because it’s in default-src. It isn’t. Once script-src and connect-src exist, those directives take over.
Fix
Allow the exact origin in the exact directive that needs it.
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN;
My rule: treat default-src as a safety net, not your main configuration.
Mistake 3: Allowing the whole SaaS domain with wildcards
A lot of teams reach for this:
connect-src 'self' https://*.unleash-hosted.com;
It works, but it’s lazy. Wildcards expand trust much more than most people realize. If you know your exact Unleash origin, use it.
Better
connect-src 'self' https://customer123.unleash-hosted.com;
Why this matters
CSP is one of the few places where being specific is cheap. If your feature flag traffic only goes to one host, don’t grant a whole domain tree.
Same advice applies if you self-host Unleash. Use the exact app or API origin actually used by the browser.
Mistake 4: Forgetting WebSockets or SSE
This one shows up when flags work at first page load but stop updating live.
Depending on your Unleash setup, updates may rely on:
fetch()polling over HTTPS- Server-Sent Events
- WebSockets
Teams often allow https://... in connect-src and assume that covers everything. It does not cover WebSockets. For that, you need wss://.
The headertest.com policy is a nice real example of this pattern:
connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com ...
See how both HTTPS and WSS are listed? That’s the right shape when a service uses both.
Fix
Content-Security-Policy:
default-src 'self';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN wss://YOUR-UNLEASH-ORIGIN;
If you only use plain HTTPS polling, don’t add wss: preemptively. Keep it tight.
Mistake 5: Adding 'unsafe-inline' because the SDK “needs it”
Most of the time, it doesn’t.
When feature flags break, people get desperate and start loosening unrelated directives. I’ve seen script-src 'unsafe-inline' added to “fix Unleash” even though the actual violation was a blocked XHR request.
That’s a bad trade.
Bad fix
script-src 'self' 'unsafe-inline' https://YOUR-UNLEASH-ORIGIN;
Real fix
Check the browser console for the exact blocked directive. If the message says something like:
Refused to connect to 'https://YOUR-UNLEASH-ORIGIN/api/frontend' because it violates the following Content Security Policy directive: "connect-src 'self'".
Then fix connect-src, not script-src.
Safer policy
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-RANDOM_NONCE';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN;
If you need nonces, use them properly. The browser’s CSP error messages are usually blunt enough to point at the right directive.
Mistake 6: Serving bootstrap data inline without a nonce
A common optimization is to bootstrap feature flags into the HTML so the app can render immediately before the SDK fetches fresh values.
Something like this:
<script>
window.__UNLEASH_BOOTSTRAP__ = {
features: [
{ name: "newNavbar", enabled: true }
]
};
</script>
That inline script will be blocked unless your CSP allows it with a nonce or hash.
Broken policy
Content-Security-Policy:
default-src 'self';
script-src 'self';
Fix with a nonce
<script nonce="{{ .CSPNonce }}">
window.__UNLEASH_BOOTSTRAP__ = {
features: [
{ name: "newNavbar", enabled: true }
]
};
</script>
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{{RANDOM_NONCE}}';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN;
If your app already uses nonces with 'strict-dynamic', keep that model consistent. Don’t carve out exceptions just for feature flags.
The official CSP docs from MDN are still the best reference for nonce and hash behavior: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
Mistake 7: Mixing server-side and browser-side Unleash requirements
Server-side Unleash usage does not need browser CSP allowances.
If your backend talks to Unleash and renders the result into HTML, the browser never connects to Unleash directly. In that setup, adding Unleash to browser connect-src is unnecessary.
I’ve seen teams copy frontend CSP exceptions into apps that only use server evaluation. That just expands the attack surface for no reason.
If Unleash is only used server-side
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self';
If the browser uses the frontend SDK
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN;
Know which architecture you actually have before changing CSP.
For Unleash-specific behavior and SDK details, check the official docs: https://docs.getunleash.io
Mistake 8: Using CSP Report-Only forever
Content-Security-Policy-Report-Only is useful while you’re rolling out Unleash or discovering which endpoints the SDK touches. But some teams stop there and never enforce anything.
That means you collect a pile of reports while the page remains fully permissive.
Rollout pattern I like
Start with:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN;
report-to csp-endpoint;
Watch for violations during real traffic, then move the stable policy to enforcement:
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN;
Report-only is a staging lane, not the destination.
A minimal CSP for browser-side Unleash
If your app:
- serves its own scripts
- uses the Unleash frontend API over HTTPS
- does not need WebSockets
- does not use inline scripts
then a reasonable baseline is:
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self' https://YOUR-UNLEASH-ORIGIN;
img-src 'self' data:;
style-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
If you bootstrap flags inline, add a nonce. If you stream updates over WebSockets, add wss://YOUR-UNLEASH-ORIGIN to connect-src.
That’s the pattern: keep script-src tight, fix connect-src deliberately, and don’t loosen unrelated directives when feature flags fail. Most Unleash CSP issues aren’t complicated. They’re just misdiagnosed.