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-scripts is usually required for JSFiddle examples to function
  • allow-same-origin may also be needed depending on how the embed behaves
  • avoid adding things like allow-top-navigation unless you absolutely need them
  • loading="lazy" is nice for performance, not security
  • referrerpolicy helps 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.