If you’ve ever dropped a Mixcloud embed into a page and watched it fail under a strict Content Security Policy, you already know the pattern: the iframe looks harmless, but CSP doesn’t care about harmless. It cares about explicit allowlists.
I’ve run into this a lot on sites that already have a decent CSP and then bolt on third-party media later. Everything is locked down, then one product request lands: “Can we embed this Mixcloud show by Friday?”
That’s where people usually make the policy worse than it needs to be.
The starting point
Here’s a real CSP header from headertest.com:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-ZGY2ZTVjNjMtZGQ4Mi00ZmI0LThiZWQtM2JmMTJmZDc0YTY1' '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'
This is a pretty typical modern setup:
default-src 'self'as a baseline- nonced scripts with
'strict-dynamic' - a narrow
frame-src object-src 'none'frame-ancestors 'none'
The relevant bit for embeds is this one:
frame-src 'self' https://consentcdn.cookiebot.com;
That means the page can only frame itself and Cookiebot’s consent UI. No Mixcloud. So if a developer adds a Mixcloud iframe, the browser blocks it.
The breakage
Here’s the embed code a developer might add:
<iframe
width="100%"
height="120"
src="https://www.mixcloud.com/widget/iframe/?hide_cover=1&mini=1&feed=%2FExampleUser%2Fexample-mix%2F"
frameborder="0">
</iframe>
With the original CSP, the browser will reject it with an error along these lines:
Refused to frame 'https://www.mixcloud.com/' because it violates the following Content Security Policy directive:
"frame-src 'self' https://consentcdn.cookiebot.com".
This is exactly what you want CSP to do. The policy is doing its job.
The bad fix is what I still see too often:
frame-src *;
Or slightly less awful:
frame-src https:;
That gets the embed working, sure. It also means any HTTPS iframe can now load. Ad tech, sketchy widgets, surprise login frames, whatever somebody pastes into a CMS next month. You didn’t solve the problem. You removed the control.
Before: too strict for the feature
Let’s keep the original policy intact and call out the part that blocks Mixcloud:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-ZGY2ZTVjNjMtZGQ4Mi00ZmI0LThiZWQtM2JmMTJmZDc0YTY1' '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';
For a page that needs a Mixcloud player, this is incomplete.
After: allow only the embed you need
The practical fix is to extend frame-src with Mixcloud’s embed origin:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-ZGY2ZTVjNjMtZGQ4Mi00ZmI0LThiZWQtM2JmMTJmZDc0YTY1' '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://www.mixcloud.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
That’s the smallest useful change.
If your only goal is “load a Mixcloud iframe on this page,” frame-src is the directive that matters. You do not need to start adding Mixcloud to script-src, style-src, or connect-src just because the embedded player itself uses scripts internally. The iframe runs in its own browsing context. Its internal resources are governed by Mixcloud’s CSP, not yours.
That distinction saves people from bloating policies.
For a deeper refresher on how directives apply, the CSP directive breakdown at csp-guide.com is handy.
The embed code I’d actually ship
I’d also tighten the iframe itself a bit:
<iframe
title="Mixcloud player"
width="100%"
height="120"
src="https://www.mixcloud.com/widget/iframe/?hide_cover=1&mini=1&feed=%2FExampleUser%2Fexample-mix%2F"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
allow="autoplay"
frameborder="0">
</iframe>
A few notes:
titlehelps accessibility.loading="lazy"avoids loading the player before the user gets near it.referrerpolicycuts down on unnecessary referrer leakage.allow="autoplay"is only needed if your UX expects it.
If you want to be stricter, you can also experiment with sandbox, but media embeds often break under aggressive sandboxing. I only add it after testing the exact provider behavior.
What tripped up the team
The common misunderstanding was this: “The player makes network requests, so we need to add Mixcloud to connect-src.”
Usually, no.
Your page is embedding https://www.mixcloud.com in an iframe. The framed document is a separate document. Its XHR/fetch/websocket requests are not checked against your page’s connect-src. They’re checked against the framed page’s own policy and browser rules.
Where you would need more CSP changes is if you used Mixcloud assets directly in your own page context. For example:
- loading a script from Mixcloud into your page
- fetching Mixcloud API data client-side from your JavaScript
- displaying Mixcloud-hosted images directly in your DOM
That’s not the normal embed case.
A tighter page-specific approach
If only one route needs Mixcloud, don’t loosen CSP site-wide.
I’m opinionated about this: global CSP exceptions age badly. Six months later nobody remembers why frame-src includes five random vendors.
If your stack supports route-level headers, use them.
Site-wide baseline
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-{RANDOM_NONCE}' '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';
Mixcloud page override
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-{RANDOM_NONCE}' '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://www.mixcloud.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
Same policy, one extra origin, only where needed.
A quick test workflow
When I roll out a change like this, I test in this order:
- Load the page with the embed.
- Check the browser console for CSP violations.
- Confirm the player renders and actually plays.
- Verify no other directives were broadened “just to make it work.”
- If possible, deploy first as Report-Only in staging.
A report-only header is useful when you’re unsure whether the provider uses more than one framing origin:
Content-Security-Policy-Report-Only: frame-src 'self' https://consentcdn.cookiebot.com https://www.mixcloud.com; report-to default-endpoint
Official documentation for CSP is here: MDN Content Security Policy.
The final lesson
For Mixcloud embeds, the real-world fix is usually boring:
- keep your existing CSP
- add
https://www.mixcloud.comtoframe-src - avoid broad wildcards
- scope the exception to the pages that need it
That’s it.
The mistake is treating every third-party embed like a reason to relax half the policy. If the integration is just an iframe, solve the iframe problem and stop there. That’s how you keep CSP useful instead of turning it into decorative security.