JSFiddle embeds look harmless until your CSP blocks them or, worse, you punch a giant hole in your policy just to make one iframe work.
I’ve seen teams “fix” this by slapping frame-src * or default-src https: into production. That works, but it also guts the point of having CSP in the first place. If you only need to embed JSFiddle, you should allow exactly JSFiddle and nothing else.
What a JSFiddle embed actually needs
A typical JSFiddle embed is just an iframe:
<iframe
width="100%"
height="300"
src="https://jsfiddle.net/user/demo/embedded/result,js,html,css/"
allowfullscreen="allowfullscreen"
allowpaymentrequest
frameborder="0">
</iframe>
From a CSP perspective, the main directive involved is:
frame-src— controls which URLs your page can load in<iframe>,<frame>,<embed>, and<applet>-style contexts
If your policy does not allow https://jsfiddle.net, the browser will block the iframe.
The minimal CSP change
If your site already has a solid baseline CSP, the smallest safe change is usually:
Content-Security-Policy: default-src 'self'; frame-src 'self' https://jsfiddle.net; object-src 'none'; base-uri 'self';
That means:
- your site can load its own resources by default
- frames can come from your own origin and JSFiddle
- plugins are disabled
<base>tag injection is restricted
If you don’t use any same-origin iframes, you can be stricter:
Content-Security-Policy: default-src 'self'; frame-src https://jsfiddle.net; object-src 'none'; base-uri 'self';
That’s the version I’d start with.
Don’t rely on default-src for this
Yes, default-src acts as a fallback for frame-src when frame-src is absent. But I don’t like leaving this implicit. If you care enough to allow a third-party iframe, declare it explicitly.
Bad:
Content-Security-Policy: default-src 'self' https://jsfiddle.net;
Better:
Content-Security-Policy: default-src 'self'; frame-src https://jsfiddle.net;
Explicit policies are easier to audit and much harder to accidentally weaken later.
For directive behavior details, the official CSP docs are the right source, and csp-guide.com is handy for quick explanations.
A realistic existing policy
Here’s the real CSP 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-YzZkZTA5Y2EtZGJmMS00NDcxLTkyNTgtNjhjYjZlNzE4Njlk' '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'
If that site wanted to allow JSFiddle embeds, the safe modification would be to extend only frame-src:
content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-YzZkZTA5Y2EtZGJmMS00NDcxLTkyNTgtNjhjYjZlNzE4Njlk' '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://jsfiddle.net; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
That’s the move. Not script-src. Not default-src https:. Just frame-src.
Server config examples
Nginx
add_header Content-Security-Policy "default-src 'self'; frame-src 'self' https://jsfiddle.net; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'" always;
If you already have a more complete policy, just add JSFiddle to frame-src:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$csp_nonce' 'strict-dynamic'; style-src 'self'; 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'" always;
Apache
Header always set Content-Security-Policy "default-src 'self'; frame-src 'self' https://jsfiddle.net; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'"
Express.js with Helmet
import express from "express";
import helmet from "helmet";
const app = express();
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
frameSrc: ["'self'", "https://jsfiddle.net"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameAncestors: ["'self'"],
},
})
);
app.get("/", (req, res) => {
res.send(`
<!doctype html>
<html>
<body>
<h1>JSFiddle embed test</h1>
<iframe
width="100%"
height="300"
src="https://jsfiddle.net/user/demo/embedded/result,js,html,css/"
frameborder="0">
</iframe>
</body>
</html>
`);
});
app.listen(3000);
Add iframe sandboxing too
CSP decides where the frame can come from. It does not restrict what the framed page can do inside the iframe nearly as much as sandbox does.
If you embed third-party content, use sandbox unless you have a strong reason not to.
Example:
<iframe
src="https://jsfiddle.net/user/demo/embedded/result,js,html,css/"
width="100%"
height="300"
sandbox="allow-scripts allow-same-origin"
referrerpolicy="strict-origin-when-cross-origin"
loading="lazy">
</iframe>
A few notes:
allow-scriptsis usually required for JSFiddle examples to functionallow-same-originmay also be needed depending on how the embed behaves- avoid adding things like
allow-top-navigationunless you absolutely need them loading="lazy"is nice for performance, not securityreferrerpolicyhelps reduce what you leak to the third party
If the embed works without allow-same-origin, remove it. I generally test with the smallest sandbox first:
sandbox="allow-scripts"
Then add capabilities only when the embed breaks.
Common mistakes
1. Allowing JSFiddle in script-src
This does nothing for iframe loading.
Wrong:
Content-Security-Policy: default-src 'self'; script-src 'self' https://jsfiddle.net;
Right:
Content-Security-Policy: default-src 'self'; frame-src https://jsfiddle.net;
2. Using wildcards you don’t need
Don’t do this:
Content-Security-Policy: frame-src https:;
Or this:
Content-Security-Policy: frame-src *;
That allows way too much. Third-party frames are one of the easiest ways to expand your attack surface.
3. Forgetting frame-ancestors
frame-src controls what you load. frame-ancestors controls who can frame you.
If you don’t want your own site embedded elsewhere, set:
Content-Security-Policy: frame-ancestors 'none';
Or if trusted same-origin framing is required:
Content-Security-Policy: frame-ancestors 'self';
That headertest.com policy uses:
frame-ancestors 'none'
I like that default for most sites.
4. Debugging the wrong page
If the iframe is blocked, check the browser console on your page. The violation usually looks something like:
Refused to frame 'https://jsfiddle.net/' because it violates the following Content Security Policy directive: "frame-src 'self'".
That message tells you exactly which directive is failing.
A practical production policy
If your site needs normal app resources and JSFiddle embeds, this is a reasonable baseline:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{{RANDOM_NONCE}}' 'strict-dynamic';
style-src 'self';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self';
frame-src https://jsfiddle.net;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
If you truly need inline styles, say so explicitly and accept the tradeoff. Don’t casually cargo-cult 'unsafe-inline' from old examples.
Testing safely with Report-Only
If you’re adding JSFiddle to an existing CSP and want to check for breakage first, use report-only mode:
Content-Security-Policy-Report-Only: default-src 'self'; frame-src 'self' https://jsfiddle.net; object-src 'none'; base-uri 'self';
This won’t block anything. It only reports violations in the console and through reporting endpoints if configured.
I use report-only when tightening policies, not when shipping obvious allowlist changes. For a simple frame-src https://jsfiddle.net addition, it’s usually low risk, but it’s still useful on large pages full of old widgets.
The short version
If you want JSFiddle embeds to work under CSP, add JSFiddle to frame-src and stop there unless the browser tells you otherwise.
Good:
Content-Security-Policy: default-src 'self'; frame-src https://jsfiddle.net; object-src 'none'; base-uri 'self';
Better with hardening:
Content-Security-Policy: default-src 'self'; frame-src https://jsfiddle.net; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';
And pair it with a sandboxed iframe:
<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">
</iframe>
That gets you a working embed without turning your CSP into decorative security.