CSP for CodePen Embeds: Options, Tradeoffs, and Fixes

Table of Contents

CodePen embeds look harmless until your CSP starts blocking them.

That usually happens the first time you paste CodePen’s embed script into a site with a reasonably locked-down policy. Suddenly the pen doesn’t render, the console fills with CSP violations, and you’re stuck deciding whether to weaken your policy or drop the embed.

I’ve had to make this tradeoff a few times. The short version: if you want CodePen on a production site, the safest path is almost always the iframe embed, not the JavaScript embed. The rest of the guide is about why.

The two ways people embed CodePen

You’ll usually see one of these:

1. JavaScript embed

CodePen gives you markup like this:

<p
  class="codepen"
  data-height="300"
  data-default-tab="html,result"
  data-slug-hash="abc123"
  data-user="someuser">
  See the Pen <a href="https://codepen.io/someuser/pen/abc123">Example</a>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

This script scans the page and replaces the placeholder with the embedded pen.

2. Plain iframe embed

You can also embed directly with an iframe:

<iframe
  height="300"
  style="width: 100%;"
  scrolling="no"
  title="Example"
  src="https://codepen.io/someuser/embed/abc123?default-tab=html%2Cresult"
  frameborder="no"
  loading="lazy"
  allowtransparency="true"
  allowfullscreen="true">
</iframe>

Both work. They are not equally friendly to CSP.

The CSP problem with CodePen embeds

CSP cares about how third-party content gets onto your page.

  • A third-party script is governed by script-src
  • A third-party iframe is governed by frame-src
  • Inline styles or scripts used by the embed can pull in style-src and nonce/hash requirements
  • If your site uses a strict nonce-based CSP with 'strict-dynamic', the behavior changes again

A real-world CSP can already be pretty tight. For example, the policy currently visible on HeaderTest looks like this:

content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-NjhmZjBhZmItYjUxYi00MGEzLWE3NjEtZDUxZjE4N2JiM2Zi' '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'

Try dropping a CodePen embed into a site with a policy like that and it will fail immediately, because neither CodePen script origins nor CodePen frame origins are allowed.

Option 1: Allow the CodePen embed script

If you want the official JavaScript embed, you need to allow CodePen’s script origin in script-src.

A minimal example might look like this:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cpwebassets.codepen.io;
  frame-src https://codepen.io;
  style-src 'self';
  img-src 'self' https: data:;
  object-src 'none';
  base-uri 'self';

Pros

  • Easiest drop-in if you’re using CodePen’s default embed snippet
  • CodePen handles rendering details for you
  • Can feel more flexible in CMS-driven content where authors paste standard snippets

Cons

  • You are allowing a third-party script into your page
  • That script runs with the privileges of your origin, subject to CSP
  • Strict CSP setups often need extra work, especially with nonces and 'strict-dynamic'
  • More likely to break if CodePen changes asset hosts or embed behavior

This is the part I’m opinionated about: third-party scripts are expensive from a security perspective. Not just CSP-wise. They add supply-chain risk, privacy questions, performance drag, and debugging pain.

If your team worked hard to get to a nonce-based script-src with 'strict-dynamic', adding a random external embed script is usually a step backward.

What about nonce-based CSP?

If your CSP looks like this:

script-src 'self' 'nonce-rAnd0m' 'strict-dynamic';

then adding https://cpwebassets.codepen.io may not behave the way people expect. In modern browsers, 'strict-dynamic' shifts trust to nonce- or hash-authorized scripts. Host allowlists become less relevant for script loading.

So if you inject this:

<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

without a valid nonce, it will still be blocked.

You’d need something more like:

<script
  nonce="{{ .CSPNonce }}"
  async
  src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

That works technically, but now you’re wiring third-party embeds into your nonce pipeline. I generally avoid that unless there’s a strong product reason.

Option 2: Use a direct iframe embed

This is the cleaner option for most sites.

CSP example:

Content-Security-Policy:
  default-src 'self';
  frame-src https://codepen.io;
  script-src 'self';
  style-src 'self';
  img-src 'self' https: data:;
  object-src 'none';
  base-uri 'self';

And the embed:

<iframe
  src="https://codepen.io/someuser/embed/abc123?default-tab=html%2Cresult"
  title="CodePen demo"
  loading="lazy"
  style="width:100%;height:320px;border:0;"
  sandbox="allow-scripts allow-same-origin"
  referrerpolicy="no-referrer-when-downgrade">
</iframe>

Pros

  • You only need frame-src, not third-party script-src
  • Keeps third-party code out of your page’s execution context
  • Easier to reason about and audit
  • Fits strict CSP deployments much better
  • Usually simpler to maintain long term

Cons

  • Less “automatic” than the script embed
  • You may need to manage sizing or responsiveness yourself
  • Some CodePen embed features are nicer with the official script
  • sandbox settings can get fiddly depending on what you need

For most developer sites, docs, and blogs, this is the sweet spot. If the content is basically a live demo, iframe it and move on.

Option 3: Self-host screenshots or static fallbacks

This isn’t a true embed, but it’s worth considering.

You can link to the CodePen and show:

  • a screenshot
  • a GIF
  • a local recreation of the demo
  • a “View on CodePen” button

Pros

  • No CSP exceptions for CodePen at all
  • Best security and performance profile
  • No third-party runtime dependency

Cons

  • Not interactive
  • More manual work
  • Can drift out of sync with the actual pen

I use this when the pen is nice-to-have, not core to the page.

Best security: no live embed

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data: https:;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

No CodePen support here. Just link out.

Best balance: iframe embed

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data: https:;
  frame-src 'self' https://codepen.io;
  object-src 'none';
  base-uri 'self';
  form-action 'self';

This is the one I’d choose first.

Most flexible: official script embed

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic' https://cpwebassets.codepen.io;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  frame-src 'self' https://codepen.io;
  object-src 'none';
  base-uri 'self';

This can work, but I’d only go here if the iframe route is insufficient.

If you want ready-made policy patterns, csp-examples.com is a good place to grab a starting point and adapt it.

Common mistakes

Forgetting frame-src

If you use an iframe embed and only update script-src, nothing changes. The browser will block the frame.

Allowing too much

Don’t “fix” CodePen by adding this:

script-src * 'unsafe-inline' 'unsafe-eval';
frame-src *;

That’s not a fix. That’s giving up.

Mixing default-src assumptions

If frame-src is missing, default-src can act as fallback. If default-src 'self' is set, CodePen frames are blocked unless explicitly allowed.

Breaking strict CSP with inline snippets

If your templating system injects inline scripts around embeds, those need nonces or hashes too. The CodePen script itself may not be the only issue.

My take

Here’s the practical ranking:

  1. Direct iframe embed — best default
  2. Static fallback with outbound link — best if security is the top priority
  3. Official CodePen script embed — only when you really need it

If your site already has a mature CSP, especially one using nonces and 'strict-dynamic', avoid adding third-party embed scripts unless the product value is obvious. A plain iframe keeps the blast radius smaller and the policy easier to understand.

That matters more than people think. CSP gets fragile fast when every team adds “just one more host” to script-src.

For CodePen, I’d keep it boring:

  • allow https://codepen.io in frame-src
  • use a direct iframe
  • skip the external embed script unless you have a concrete reason

That’s usually the least painful choice, and from experience, it’s the one you’ll regret the least six months later.