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-srcand 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-partyscript-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
sandboxsettings 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.
Recommended policies by use case
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:
- Direct iframe embed — best default
- Static fallback with outbound link — best if security is the top priority
- 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.ioinframe-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.