JSFiddle embeds look harmless until your CSP blocks them and your page turns into an empty rectangle.
I’ve seen this happen a lot: someone adds a fiddle iframe to docs, tutorials, or a demo page, then ships a tight CSP and suddenly the embed refuses to load. The browser console says something vague about frame-src, maybe script-src, and now everyone is guessing.
JSFiddle embeds are a good example of how CSP failures usually come from one or two small misunderstandings, not from some giant policy disaster. Here are the mistakes I see most often, and the fixes that actually work.
Mistake #1: Forgetting frame-src
If you embed JSFiddle with an <iframe>, the browser checks frame-src to decide whether that iframe is allowed.
A lot of teams only think about script-src because “it’s JavaScript content.” But an embed is still an iframe first. If frame-src doesn’t allow JSFiddle, the browser blocks the load before any embedded code matters.
A typical broken policy looks like this:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
And the embed:
<iframe
src="https://jsfiddle.net/user/demo/embedded/result,js,html,css/"
width="100%"
height="400"
loading="lazy">
</iframe>
That fails because frame-src falls back to default-src, which is only 'self'.
Fix
Allow JSFiddle in frame-src.
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
frame-src 'self' https://jsfiddle.net;
If you want a ready-made pattern for embed-heavy policies, the examples at https://csp-examples.com are useful as a starting point.
Mistake #2: Assuming default-src covers everything cleanly
Technically, default-src is the fallback for many fetch directives. Practically, relying on it makes debugging embeds annoying.
I’ve seen real production headers like this one from headertest.com:
content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-ZDU3NzNmN2ItMDU1Ny00ODBhLWE0ODEtM2FjZjRmYWNmYTEz' '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'
That policy is explicit about frame-src, which is good. But notice the consequence: only self and https://consentcdn.cookiebot.com can be framed. A JSFiddle embed would be blocked even though several third-party domains are allowed elsewhere.
This is exactly why I prefer being explicit for embeds instead of hoping default-src behavior lines up with what the page needs.
Fix
Add the exact framed origin you need:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
frame-src 'self' https://jsfiddle.net;
object-src 'none';
base-uri 'self';
Mistake #3: Whitelisting the wrong JSFiddle host
People often add https://www.jsfiddle.net because they’re used to www subdomains. The embed URL usually comes from https://jsfiddle.net, not www.jsfiddle.net.
CSP source matching is literal enough that this matters.
Broken:
frame-src 'self' https://www.jsfiddle.net;
Actual iframe:
<iframe src="https://jsfiddle.net/user/demo/embedded/result/"></iframe>
Fix
Match the real origin:
frame-src 'self' https://jsfiddle.net;
If you use multiple subdomains for some service, then a wildcard may make sense. For JSFiddle embeds, I’d keep it exact unless you’ve verified otherwise.
Mistake #4: Trying to solve iframe issues with script-src
This one wastes a lot of time.
You see a blocked JSFiddle embed, then somebody starts adding domains to script-src:
script-src 'self' https://jsfiddle.net;
That usually does nothing for the iframe load itself.
The parent page’s script-src controls scripts executed by the parent page. The iframe load is governed by frame-src. The embedded page enforces its own CSP internally.
Fix
Separate the concerns:
- Use
frame-srcto allow the embed origin. - Use your own
script-srconly for scripts your page runs. - Don’t loosen
script-srcunless the parent page actually needs it.
A clean policy for a page with a JSFiddle embed might look like this:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-r4nd0m123';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
frame-src 'self' https://jsfiddle.net;
object-src 'none';
base-uri 'self';
form-action 'self';
Mistake #5: Blocking the embed with an over-aggressive sandbox
A lot of developers add sandbox to every iframe, which is usually a good instinct. Then they accidentally remove permissions the embed actually needs.
For example:
<iframe
src="https://jsfiddle.net/user/demo/embedded/result/"
sandbox=""
width="100%"
height="400">
</iframe>
That is extremely restrictive. Depending on the fiddle content, it may break scripts, forms, popups, or same-origin behavior inside the embed.
Fix
Use the minimum sandbox permissions that still let the fiddle work.
A more realistic setup:
<iframe
src="https://jsfiddle.net/user/demo/embedded/result,js,html,css/"
sandbox="allow-scripts allow-same-origin"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
width="100%"
height="400">
</iframe>
If the embedded demo needs form submission or popups, you may need more:
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
I’d start strict, test the specific fiddle, then add only what’s necessary. Blindly using allow-top-navigation or broad sandbox permissions is sloppy.
Mistake #6: Forgetting frame-ancestors when you actually need your page embeddable
This one is less about JSFiddle inside your page and more about whether your page can itself be embedded elsewhere.
The headertest.com policy includes:
frame-ancestors 'none';
That means nobody can frame that page. Fine for a standalone app. Not fine if you expect your documentation page with a JSFiddle embed to appear inside another portal, LMS, or docs shell.
People mix this up with frame-src, but they are different:
frame-src: what your page is allowed to embedframe-ancestors: who is allowed to embed your page
Fix
If your page must be embeddable by a trusted site, set frame-ancestors accordingly.
frame-ancestors 'self' https://docs.example.com;
If it should never be framed, keep it tight:
frame-ancestors 'none';
Just don’t confuse the directive and spend an hour debugging the wrong thing.
Mistake #7: Using X-Frame-Options as if it solves embed policy
I still see teams trying to control outgoing iframe permissions with X-Frame-Options. That header does not replace frame-src.
X-Frame-Options only controls whether your page can be embedded by someone else. It does not tell the browser what your page may embed.
Fix
Use the right control for the right job:
frame-srcfor JSFiddle embedsframe-ancestorsfor who can embed your page
If you still send X-Frame-Options, treat it as legacy defense for inbound framing, not as a CSP substitute.
Mistake #8: Debugging in enforce mode only
If you’re tuning CSP around third-party embeds, jumping straight into blocking mode is painful. You end up breaking production pages just to discover one missing source.
Fix
Start with report-only while you verify the exact sources involved.
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'nonce-r4nd0m123';
style-src 'self' 'unsafe-inline';
frame-src 'self' https://jsfiddle.net;
report-to csp-endpoint;
Then watch the browser console and your reports. Once the page works and the reports are clean, move the policy into enforcing mode.
A solid baseline policy for JSFiddle embeds
If you just need a practical starting point, this is reasonable for many docs or blog pages:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-r4nd0m123';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self';
frame-src 'self' https://jsfiddle.net;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'self';
And the iframe:
<iframe
src="https://jsfiddle.net/user/demo/embedded/result,js,html,css/"
sandbox="allow-scripts allow-same-origin"
loading="lazy"
width="100%"
height="420"
title="JSFiddle demo">
</iframe>
What I’d check first when a JSFiddle embed fails
My quick triage list is always the same:
- Is the iframe URL really on
https://jsfiddle.net? - Does
frame-srcexplicitly allow that origin? - Is a restrictive
sandboxbreaking the embedded demo? - Are you confusing
frame-srcwithframe-ancestors? - Are you loosening
script-srcfor no reason?
Most JSFiddle CSP problems collapse to one of those five.
For the formal directive behavior, the official CSP docs on MDN are still the best reference point: https://developer.mozilla.org/docs/Web/HTTP/CSP.
The trick is not making your policy “support embeds” in some vague way. The trick is being precise. Allow the exact frame origin, keep the rest tight, and don’t cargo-cult extra sources into script-src because an iframe looked like JavaScript.