CSP for SoundCloud Embeds: A Real-World Before/After
Table of Contents
If you’ve ever dropped a SoundCloud player into a page and thought “it’s just an iframe, what could go wrong?”, CSP will answer that pretty quickly.
I’ve seen this exact failure pattern on otherwise well-locked-down sites: strong baseline policy, analytics and consent tooling carefully whitelisted, object-src 'none', frame-ancestors 'none', all the good stuff. Then somebody adds a SoundCloud embed and the player just shows a blank box or refuses to load.
The reason is simple: a decent CSP usually blocks third-party frames by default.
Here’s a real header from official CSP examples style setups and from production-grade sites with consent tooling, based on the Headertest sample you provided:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-ODgwYzc0YmYtMTBiOS00NWYxLTk0MTYtODJmNDRjZjE1OTI2' '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'
That policy is pretty reasonable. It’s not reckless. It’s doing what CSP should do: only allow known third-party integrations.
It also blocks SoundCloud completely.
The breakage
A typical SoundCloud embed looks like this:
<iframe
title="SoundCloud player"
width="100%"
height="166"
scrolling="no"
frameborder="no"
allow="autoplay"
src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/123456789&color=%23ff5500">
</iframe>
If your CSP only allows:
frame-src 'self' https://consentcdn.cookiebot.com;
the browser will reject that iframe. In DevTools, you’ll usually see something like:
Refused to frame 'https://w.soundcloud.com/' because it violates the following Content Security Policy directive:
"frame-src 'self' https://consentcdn.cookiebot.com".
That’s the most common failure. But SoundCloud embeds can trigger a second round of confusion: teams often start loosening unrelated directives like script-src, style-src, or connect-src trying to “fix” the player. Usually that’s unnecessary.
For a plain iframe embed, your main CSP concern is frame-src.
Before: a secure policy that breaks embeds
Let’s use the real-world baseline almost as-is.
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-ODgwYzc0YmYtMTBiOS00NWYxLTk0MTYtODJmNDRjZjE1OTI2' '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'
And the page template:
<article class="podcast-episode">
<h2>Episode 42</h2>
<p>Listen on SoundCloud:</p>
<iframe
title="SoundCloud player"
width="100%"
height="166"
scrolling="no"
frameborder="no"
allow="autoplay"
src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/123456789">
</iframe>
</article>
Result: blocked.
Why? Because frame-src is explicit, and w.soundcloud.com is not on the list.
The bad fix I see too often
Someone gets the console error and patches the wrong directive:
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://w.soundcloud.com https://*.soundcloud.com;
That does not solve the iframe problem. It just weakens your policy.
Same story with this:
default-src 'self' https: data:;
Yes, it probably makes the embed work. It also turns your policy into mush.
If I’m reviewing a CSP and I see a “fix” like that, I assume the team was under pressure and guessed. CSP punishes guessing.
After: the minimal fix
For a standard SoundCloud iframe, start by allowing only the frame origin you actually need.
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-ODgwYzc0YmYtMTBiOS00NWYxLTk0MTYtODJmNDRjZjE1OTI2' '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 https://w.soundcloud.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
That’s usually enough.
A lot of teams overcomplicate CSP changes because third-party embeds feel opaque. But in this case, the boundary is clean: your page loads an iframe from https://w.soundcloud.com, and the browser checks frame-src.
When child-src shows up in older policies
Some older CSPs still use child-src. If you have both, modern browsers generally prefer frame-src for frames. I’d be explicit and keep frame-src.
Example legacy policy:
Content-Security-Policy:
default-src 'self';
child-src 'self';
If you’re modernizing it for SoundCloud, do this instead:
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://w.soundcloud.com;
object-src 'none';
base-uri 'self';
Cleaner and less ambiguous.
Real-world gotcha: embed previews vs production pages
One thing that trips people up is testing the embed on a staging CMS preview page that has a much looser CSP than production. Everything works in preview, then breaks after deploy.
I’ve hit this with marketing teams more than once. The editor UI allows arbitrary embed code, but the production frontend has a locked frame-src. Nobody notices until launch day.
My rule: test embeds against the actual production CSP early, ideally in Content-Security-Policy-Report-Only first.
For example:
Content-Security-Policy-Report-Only:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-ODgwYzc0YmYtMTBiOS00NWYxLTk0MTYtODJmNDRjZjE1OTI2' '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 https://w.soundcloud.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
That lets you confirm whether SoundCloud needs anything beyond frame-src before enforcing the change.
What not to allow
If your only requirement is “embed a SoundCloud player,” I would not start with:
https://*.soundcloud.comeverywhere- broad
https:fallbacks unsafe-inlineorunsafe-evalinscript-srcmedia-srcchanges unless you know your own page is directly loading media
Remember: the player runs inside SoundCloud’s iframe origin. You do not need to bless all of its internals in your top-level page policy.
That separation is the whole point.
A tighter final example
If you want a focused policy snippet for a page that supports SoundCloud embeds, this is a sensible pattern:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-random123' 'strict-dynamic';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self';
frame-src 'self' https://w.soundcloud.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
And if your site already has analytics, consent, and other vendors, just add https://w.soundcloud.com to the existing frame-src list rather than redesigning the whole policy.
Practical checklist
When a SoundCloud embed fails under CSP, I’d check these in order:
- Is the iframe
srcactually onhttps://w.soundcloud.com? - Does
frame-srcincludehttps://w.soundcloud.com? - Are you testing the same CSP header in production as in preview?
- Are you “fixing” the wrong directive based on guesswork?
- Did anyone weaken
default-srcas a shortcut?
That last one is worth calling out. A narrow frame-src exception is the right tradeoff here. A broad default-src https: is not.
Final before/after diff
Before:
frame-src 'self' https://consentcdn.cookiebot.com;
After:
frame-src 'self' https://consentcdn.cookiebot.com https://w.soundcloud.com;
That’s the real fix most teams need.
If you want ready-to-use policy patterns, official documentation for CSP is still the best reference, and https://csp-examples.com is handy when you need example policies without inventing them from scratch.
The main lesson here is boring, which usually means it’s right: don’t loosen CSP globally for a single embed. Add the exact frame origin you need, verify it in report-only mode, and leave the rest of the policy alone.