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-srccontrols what your page can embedframe-ancestorscontrols 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:
- What origin is the iframe using?
- What resources does my page load around that iframe?
- 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.