Tidal embeds look simple right up until your Content-Security-Policy blocks them.
That’s the usual story with third-party media: the product team pastes an iframe, it works locally, then production CSP shuts it down and everyone blames security. The real fix is to decide what kind of CSP you want to run: strict and minimal, or practical and easier to maintain.
For Tidal embeds, that tradeoff matters because you’re almost always dealing with an iframe, not a script widget you fully control. That changes which directives matter most.
The short version
If you embed Tidal with an iframe, the directive you’ll care about first is usually:
frame-src
Depending on your page, you may also need to think about:
child-srcfor legacy fallback behaviorimg-srcif Tidal content pulls remote artwork in ways that affect your pagemedia-srcif you ever move beyond pure iframe embeddingconnect-srcif you use Tidal APIs directly from the browserframe-ancestorsif you’re controlling who can embed your site
If you only need a plain iframe, your CSP can stay pretty tight.
A useful baseline: what a real CSP looks like
Here’s a real-world 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-OWNmYzhlYjktMWVmOS00Yzk0LWI3ZDYtM2I3MGU4ODM4ZTM5' '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'
I like this example because it shows a common production pattern:
- tight defaults
- explicit third-party allowances
object-src 'none'base-uri 'self'frame-ancestors 'none'
To support Tidal embeds, you’d extend frame-src rather than loosening everything else.
Option 1: Minimal CSP just for Tidal iframe embeds
If all you need is a Tidal player iframe, keep it boring.
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://embed.tidal.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'self';
Example HTML:
<iframe
src="https://embed.tidal.com/tracks/123456789"
width="100%"
height="180"
allow="encrypted-media"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin">
</iframe>
Pros
- Very small attack surface
- Easy to reason about
- Easy to review during security audits
- Doesn’t accidentally permit Tidal scripts, XHR, or images outside the iframe boundary
Cons
- Assumes Tidal only needs that embed origin
- Can break if Tidal changes embed hostnames or delivery patterns
- Gives you less room for product teams to “just add one more thing”
This is my default recommendation. If the embed is truly just an iframe, don’t preemptively allow half the internet.
Option 2: Practical CSP for teams that expect change
Some teams hate touching CSP after launch. I get it. If your org moves slowly, a slightly broader policy can reduce break/fix churn.
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://embed.tidal.com https://listen.tidal.com;
img-src 'self' data: https:;
media-src 'self' https:;
connect-src 'self' https:;
object-src 'none';
base-uri 'self';
frame-ancestors 'self';
Pros
- More resilient if Tidal embed behavior changes
- Less likely to break during vendor-side updates
- Works better if your page mixes multiple media providers
Cons
- Much broader than necessary
connect-src https:is a pretty big allowancemedia-src https:can quietly bless more origins than your team realizes- Harder to answer “exactly what third parties can this page talk to?”
This approach is acceptable if you know your frontend is already vendor-heavy. I still wouldn’t call it clean.
Option 3: Extend an existing production CSP
Most real apps already have analytics, consent tools, and internal APIs. In that case, don’t redesign your policy for Tidal. Add the smallest possible change.
Starting from the headertest-style header, you’d do this:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-OWNmYzhlYjktMWVmOS00Yzk0LWI3ZDYtM2I3MGU4ODM4ZTM5' '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://embed.tidal.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
That’s usually the right move.
Pros
- Minimal change to a known-good policy
- Lower risk of collateral breakage
- Easy diff for code review
- Keeps the Tidal allowance scoped to framing only
Cons
- You inherit any existing policy problems
- If your current CSP is messy, this won’t magically fix it
- Teams may cargo-cult more third-party entries into
frame-src
If you’re maintaining a production CSP, small diffs win.
Strict vs practical: the real comparison
Here’s the version I’d use in a decision doc.
Strict policy
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://embed.tidal.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'self';
Good for:
- security-sensitive apps
- internal platforms
- teams with disciplined CSP maintenance
- pages where Tidal is the only third-party embed
Bad for:
- organizations that rarely update headers
- messy CMS environments
- pages with lots of marketing tooling and unknown future embeds
Practical policy
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://embed.tidal.com https://listen.tidal.com;
img-src 'self' data: https:;
media-src 'self' https:;
connect-src 'self' https:;
object-src 'none';
base-uri 'self';
frame-ancestors 'self';
Good for:
- fast-moving product teams
- pages with multiple media vendors
- lower-friction deployments
Bad for:
- environments with strict third-party governance
- teams that need precise source inventories
- anyone trying to keep CSP genuinely restrictive
Common mistakes with Tidal embeds
I’ve seen these a lot.
1. Adding Tidal to script-src when you only use an iframe
Don’t do this unless you actually load JavaScript from Tidal in the top-level page.
Bad:
script-src 'self' https://embed.tidal.com;
Better:
frame-src 'self' https://embed.tidal.com;
2. Relying on default-src and hoping it covers everything
Technically, fetch directives can fall back to default-src, but explicit directives are clearer and safer. If framing is allowed, say so with frame-src.
If you want a refresher on directive fallback behavior, the CSP reference at https://csp-guide.com is handy.
3. Forgetting frame-ancestors
frame-src controls what you load. frame-ancestors controls who can load you.
Those are completely different controls.
Example:
frame-ancestors 'none';
That blocks other sites from embedding your page, even if your page itself embeds Tidal.
4. Testing only with meta CSP
You’ll get more consistent behavior from an HTTP response header. Use headers in production.
Example in Nginx:
add_header Content-Security-Policy "default-src 'self'; frame-src 'self' https://embed.tidal.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self';" always;
Example in Express:
app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy",
"default-src 'self'; frame-src 'self' https://embed.tidal.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self';"
);
next();
});
My recommendation
For most developer teams, I’d ship this first:
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://embed.tidal.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'self';
Then expand only if real breakage proves you need more.
That approach keeps the blast radius small and the policy understandable. CSPs usually get worse by trying to predict every future vendor need. I’d rather start tight, watch reports, and make deliberate changes.
If you already have a mature CSP in production, just add Tidal to frame-src and leave the rest alone.
For directive-specific details, the official CSP spec and MDN docs are still the best primary references, and https://csp-guide.com is useful when you want quick directive explanations without digging through spec language.