Loom embeds look simple: paste an iframe, ship it, move on.

Then CSP blocks it, the video area goes blank, and somebody “fixes” it by slapping https: into frame-src or loosening half the policy. I’ve seen this happen more than once. Loom is exactly the kind of third-party embed that exposes weak CSP habits: developers guess at directives, over-allow sources, or forget that an iframe usually pulls in more than one origin.

Here’s where people usually get it wrong, and how I’d fix it without turning CSP into decoration.

The basic Loom embed problem

A typical Loom embed looks like this:

<iframe
  src="https://www.loom.com/embed/VIDEO_ID"
  frameborder="0"
  allowfullscreen
></iframe>

If your CSP doesn’t allow Loom in frame-src, the browser blocks it.

A minimal policy update usually starts here:

Content-Security-Policy: default-src 'self'; frame-src 'self' https://www.loom.com;

That’s the obvious part. The less obvious part is that many teams assume this is the only change needed. It often isn’t.

Mistake #1: Only updating default-src

A common policy starts with something like this real-world example from headertest.com:

content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-Zjg3Yzc0OTUtM2ZmYi00Mjg1LWE5ZGYtMmFkYzI5M2U3ZjUz' '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 see developers add Loom to default-src and wonder why embeds still fail:

default-src 'self' https://www.loom.com

That does nothing if frame-src is already defined. Once you set a specific fetch directive, it takes precedence over default-src.

Fix

Add Loom to frame-src, not just default-src:

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://www.loom.com;
  object-src 'none';
  base-uri 'self';

If you want a deeper refresher on directive fallback behavior, https://csp-guide.com has a good breakdown.

Mistake #2: Forgetting that embeds may use more than one origin

This is the one that burns time in production.

You allow https://www.loom.com in frame-src, the iframe loads, but some functionality still breaks. Depending on how Loom serves player assets or supporting requests, you may need to allow additional sources for media, images, or child resources loaded inside your page context.

Now, to be precise: resources loaded inside the iframe document are governed by Loom’s CSP, not yours. But your page can still need the right permissions if you use Loom thumbnails, preview images, API calls, or custom wrapper components.

For example, teams often render a clickable poster image before creating the iframe:

<img src="https://cdn.loom.com/sessions/thumbnails/abc123.jpg" alt="Video preview">

If your policy says:

img-src 'self';

that thumbnail gets blocked even though the iframe source is allowed.

Fix

Audit what you actually load on your own page, not just the iframe:

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

Don’t blindly allow https: in img-src unless you genuinely accept any HTTPS image source. The headertest.com policy does this:

img-src 'self' data: https:;

That’s convenient, but broad. For a security-focused app, I’d rather list exact domains where possible.

Mistake #3: Using frame-ancestors when you meant frame-src

This confusion never dies.

  • frame-src controls what your page can embed
  • frame-ancestors controls who can embed your page

If Loom videos aren’t appearing, changing frame-ancestors won’t help.

I’ve seen people try this:

frame-ancestors 'self' https://www.loom.com;

That does not allow your page to load a Loom iframe. It allows those origins to embed you.

Fix

Use the right directive:

frame-src 'self' https://www.loom.com;

And keep frame-ancestors strict based on your actual clickjacking requirements:

frame-ancestors 'none';

That headertest.com header does this part well. If your site should never be framed, frame-ancestors 'none' is a solid default.

For the exact behavior, see the CSP docs on https://developer.mozilla.org/.

Mistake #4: Whitelisting too much to “make it work”

This is the panic fix:

frame-src *;
img-src * data: blob:;
script-src * 'unsafe-inline' 'unsafe-eval';

Yes, the embed probably works. Your CSP is now mostly theater.

For Loom, you usually do not need to weaken script-src just to embed a normal iframe. If your embed strategy requires inline bootstrapping code, fix that separately with nonces or hashes. Don’t use a third-party iframe as an excuse to open up scripts globally.

Fix

Start narrow and expand only when the browser tells you exactly what got blocked.

A sane policy might look like this:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-rAnd0mNonce123';
  style-src 'self';
  img-src 'self' data: https://cdn.loom.com;
  frame-src 'self' https://www.loom.com;
  connect-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

That’s boring, and boring is good.

Mistake #5: Ignoring CSP violation reports and browser console errors

A lot of CSP debugging still happens by guessing. That’s unnecessary.

When a Loom embed is blocked, the browser usually tells you exactly which directive failed. If you’re not checking DevTools, you’re making this harder than it needs to be.

Typical error:

Refused to frame 'https://www.loom.com/' because it violates the following Content Security Policy directive: "frame-src 'self'".

That message is gold. Don’t skip it.

Fix

Use Report-Only first when rolling out changes:

Content-Security-Policy-Report-Only:
  default-src 'self';
  frame-src 'self' https://www.loom.com;
  report-to default-endpoint;

Or use the older reporting directive if that’s what your stack supports:

Content-Security-Policy-Report-Only:
  default-src 'self';
  frame-src 'self' https://www.loom.com;
  report-uri /csp-report;

Then collect violations before enforcing. The official CSP docs cover both reporting mechanisms on MDN and the CSP spec.

Mistake #6: Forgetting sandbox interactions

Some teams wrap all third-party iframes in a restrictive sandbox attribute, which is usually a good instinct. Then the Loom player loses features like fullscreen, playback interactions, or scripts.

Example:

<iframe
  src="https://www.loom.com/embed/VIDEO_ID"
  sandbox=""
></iframe>

That’s extremely restrictive.

Fix

If you sandbox, allow only what the player actually needs:

<iframe
  src="https://www.loom.com/embed/VIDEO_ID"
  sandbox="allow-scripts allow-same-origin allow-presentation"
  allow="fullscreen"
  allowfullscreen
></iframe>

This is separate from CSP, but people often blame CSP when the real problem is iframe sandboxing.

Mistake #7: Copy-pasting a policy without fitting it to your app

That headertest.com header is a good reminder that real CSPs grow around real dependencies:

frame-src 'self' https://consentcdn.cookiebot.com;

If that site added Loom, the correct change would be surgical:

frame-src 'self' https://consentcdn.cookiebot.com https://www.loom.com;

Not this:

frame-src 'self' https:;

And definitely not this:

default-src 'self' https:;

A CSP should reflect your application architecture, not your frustration level.

A practical policy for Loom embeds

If all you need is a standard Loom iframe and maybe a thumbnail image, I’d start here:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-REPLACE_WITH_REAL_NONCE';
  style-src 'self';
  img-src 'self' data: https://cdn.loom.com;
  frame-src 'self' https://www.loom.com;
  connect-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

And the matching HTML:

<iframe
  src="https://www.loom.com/embed/VIDEO_ID"
  allowfullscreen
  allow="fullscreen"
  loading="lazy"
  referrerpolicy="strict-origin-when-cross-origin"
></iframe>

If your app uses additional Loom assets, add them one by one based on actual violations.

My rule of thumb

When a third-party embed breaks under CSP, I ask three questions:

  1. What origin is the iframe using?
  2. What resources does my page load around that iframe?
  3. Am I fixing the right directive, or just changing random CSP lines until the error disappears?

That usually gets you to the answer fast.

For Loom, the big fix is usually simple: allow https://www.loom.com in frame-src. The bigger challenge is resisting the urge to over-broaden the policy while you debug. That’s where most mistakes happen, and where good CSPs quietly turn into bad ones.