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-src for legacy fallback behavior
  • img-src if Tidal content pulls remote artwork in ways that affect your page
  • media-src if you ever move beyond pure iframe embedding
  • connect-src if you use Tidal APIs directly from the browser
  • frame-ancestors if 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 allowance
  • media-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.