CSP for Iframely Embeds Without Breaking Your Site

Table of Contents

Iframely is one of those tools that looks simple until CSP gets involved. Paste a URL, get a rich embed, ship it. Then you turn on a strict Content Security Policy and half your embeds stop rendering, thumbnails vanish, or a provider silently fails inside an iframe.

I’ve dealt with this a few times, and the tricky part is that “Iframely embed” can mean different things:

  1. A plain iframe hosted by Iframely
  2. A script-based embed that injects markup
  3. An Iframely result that itself loads third-party content from YouTube, X, Vimeo, Spotify, and friends

That means your CSP has to account for both Iframely and whatever the final provider needs.

The core CSP directives that matter

For Iframely, these directives usually decide whether things work:

  • script-src: if you load Iframely’s embed script
  • frame-src: if the embed renders as an iframe
  • img-src: thumbnails and preview images
  • media-src: audio/video assets in some providers
  • connect-src: API calls from script-based embeds
  • style-src: inline or remote styles used by the embed
  • child-src: older fallback for frames in legacy setups

If your site uses a locked-down policy like this real 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-ZmFhN2E2OTItNmE5MC00NTZiLThkMjMtMjE3ZTU1MzBhY2Jm' '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'

Iframely won’t work out of the box. There’s no allowance for Iframely scripts, frames, or API endpoints.

If you want to inspect your live CSP and spot breakage fast, HeaderTest is handy for seeing the policy exactly as browsers receive it.

Start with the embed type

1. Iframe embed

A typical iframe embed looks like this:

<iframe
  src="https://cdn.iframe.ly/api/iframe?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dabc123&key=YOUR_KEY"
  width="640"
  height="360"
  loading="lazy"
  allowfullscreen>
</iframe>

For this style, the minimum CSP change is usually frame-src.

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://cdn.iframe.ly;
  img-src 'self' data: https:;
  object-src 'none';
  base-uri 'self';

That allows the iframe itself, but not necessarily everything inside it if the final provider gets loaded directly from another origin. Some providers stay inside the Iframely-hosted frame, others may involve redirects or nested frames. In practice, you may need:

frame-src 'self' https://cdn.iframe.ly https://*.youtube.com https://player.vimeo.com https://open.spotify.com;

That’s why I don’t recommend “just add Iframely and done.” Test actual URLs your product supports.

2. Script embed

Another common pattern loads Iframely’s JavaScript:

<a
  data-iframely-url="//iframely.net/abcdef?card=small"
  href="https://example.com/some-content">
</a>

<script async src="//cdn.iframe.ly/embed.js"></script>

Now your CSP needs script-src, and often frame-src, img-src, and connect-src too.

A baseline policy for that looks like:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.iframe.ly;
  connect-src 'self' https://cdn.iframe.ly https://iframely.net;
  frame-src 'self' https://cdn.iframe.ly https://iframely.net;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  base-uri 'self';

The ugly bit here is style-src 'unsafe-inline'. Some embed libraries inject inline styles. If you can avoid that with your exact integration, do it. If not, you’re trading some strictness for functionality.

Adapting a strict production CSP

Say your current policy is close to the HeaderTest example. Here’s a realistic adjusted version for Iframely script embeds:

Content-Security-Policy:
  default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
  script-src 'self' 'nonce-ZmFhN2E2OTItNmE5MC00NTZiLThkMjMtMjE3ZTU1MzBhY2Jm' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com https://cdn.iframe.ly;
  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 https://cdn.iframe.ly https://iframely.net;
  frame-src 'self' https://consentcdn.cookiebot.com https://cdn.iframe.ly https://iframely.net;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

That’s still probably incomplete for real-world embeds. If users can paste YouTube, TikTok, or Vimeo links, expect provider-specific additions.

The provider problem

Iframely is a proxy and formatter, but embedded media often still comes from the original service. That means a YouTube card may need:

frame-src https://www.youtube.com https://www.youtube-nocookie.com;
img-src https://i.ytimg.com;

A Vimeo embed may need:

frame-src https://player.vimeo.com;
img-src https://i.vimeocdn.com;

Spotify:

frame-src https://open.spotify.com;

This is where teams usually go wrong: they write one policy for Iframely’s domain and forget the downstream providers.

If you need starter policies for common embed platforms, csp-examples.com is a good place to grab patterns and then trim them down.

A practical allowlist strategy

Don’t try to support every provider on day one. That turns into https: everywhere, which defeats the point of CSP.

I prefer this approach:

  1. List the providers your app actually allows
  2. Add CSP entries only for those providers
  3. Log violations in report-only mode first
  4. Promote to enforcing once you’ve tested real content

Example: if your product only supports YouTube, Vimeo, and Spotify through Iframely:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.iframe.ly;
  connect-src 'self' https://cdn.iframe.ly https://iframely.net;
  frame-src 'self' https://cdn.iframe.ly https://iframely.net https://www.youtube.com https://www.youtube-nocookie.com https://player.vimeo.com https://open.spotify.com;
  img-src 'self' data: https://i.ytimg.com https://i.vimeocdn.com https://cdn.iframe.ly;
  style-src 'self' 'unsafe-inline';
  media-src 'self' https:;
  object-src 'none';
  base-uri 'self';
  form-action 'self';

Not perfect, but much better than frame-src https:.

Use report-only before enforcing

This saves a lot of pain.

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://cdn.iframe.ly;
  connect-src 'self' https://cdn.iframe.ly https://iframely.net;
  frame-src 'self' https://cdn.iframe.ly https://iframely.net;
  img-src 'self' data: https:;
  report-uri /csp-report;

Then collect reports server-side.

Example Node/Express endpoint:

import express from "express";

const app = express();

app.use(express.json({ type: ["application/json", "application/csp-report"] }));

app.post("/csp-report", (req, res) => {
  console.log("CSP violation:", JSON.stringify(req.body, null, 2));
  res.sendStatus(204);
});

app.listen(3000);

A typical violation for a blocked embed might look like:

{
  "csp-report": {
    "document-uri": "https://app.example.com/post/123",
    "violated-directive": "frame-src",
    "blocked-uri": "https://player.vimeo.com"
  }
}

That tells you exactly what to add.

Framework examples

Nginx

add_header Content-Security-Policy "
  default-src 'self';
  script-src 'self' https://cdn.iframe.ly;
  connect-src 'self' https://cdn.iframe.ly https://iframely.net;
  frame-src 'self' https://cdn.iframe.ly https://iframely.net https://www.youtube.com https://player.vimeo.com;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  base-uri 'self';
" always;

Express with Helmet

import express from "express";
import helmet from "helmet";

const app = express();

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "https://cdn.iframe.ly"],
        connectSrc: ["'self'", "https://cdn.iframe.ly", "https://iframely.net"],
        frameSrc: [
          "'self'",
          "https://cdn.iframe.ly",
          "https://iframely.net",
          "https://www.youtube.com",
          "https://player.vimeo.com"
        ],
        imgSrc: ["'self'", "data:", "https:"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        objectSrc: ["'none'"],
        baseUri: ["'self'"]
      }
    }
  })
);

app.listen(3000);

Common mistakes

Forgetting frame-src

This is the big one. The iframe tag exists, but the browser refuses to load it.

Allowing only Iframely, not the provider

Works for some URLs, fails for others.

Using default-src and assuming it covers everything well

It does act as a fallback, but explicit directives like script-src, frame-src, and connect-src are where real control happens.

Going straight to https:

Yes, it fixes the problem. It also weakens your policy enough that you’ll regret it later.

Ignoring frame-ancestors

This one doesn’t affect loading Iframely embeds on your page, but it does control whether your site can be framed by others. Keep it intentional.

If you’re adding Iframely to a developer product and want a sane starting point, I’d begin here and then tighten per provider:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.iframe.ly;
  connect-src 'self' https://cdn.iframe.ly https://iframely.net;
  frame-src 'self' https://cdn.iframe.ly https://iframely.net;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  media-src 'self' https:;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

Then watch report-only violations and add only the providers you actually support.

That’s the whole game with CSP and Iframely: start narrow, test real embeds, and resist the temptation to paper over everything with giant wildcards.