If you embed Typeform, SurveyMonkey, Google Forms, or similar widgets, CSP gets annoying fast.
The failure mode is usually the same: the survey box is blank, the console screams about frame-src or script-src, and someone “fixes” it by throwing https: into half the policy. That works, but it also guts the point of CSP.
I’d rather ship a tight policy and open only what the embed actually needs.
The common CSP directives for survey embeds
For most survey providers, these are the directives that matter:
script-src— loader scripts and SDKsframe-src— the actual embedded survey iframeconnect-src— API calls, analytics, websocket trafficstyle-src— inline styles or hosted CSSimg-src— logos, avatars, tracking pixelsfont-src— hosted fontsframe-ancestors— whether your page can be framed by others
If you want a deeper refresher on what each directive does, csp-guide.com is a good reference.
Minimal CSP for a Typeform embed
A standard Typeform embed usually loads:
- a script from
https://embed.typeform.com - an iframe from
https://form.typeform.com
A practical starting policy looks like this:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://embed.typeform.com;
frame-src https://form.typeform.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://embed.typeform.com https://form.typeform.com;
font-src 'self' https://embed.typeform.com;
object-src 'none';
base-uri 'self';
form-action 'self';
That’s the version I’d start with for a dedicated marketing page with one Typeform embed.
Example HTML
<div data-tf-live="01HVABCDEFG1234567890"></div>
<script src="https://embed.typeform.com/next/embed.js"></script>
Or the iframe version:
<iframe
src="https://form.typeform.com/to/AbCdEf12"
width="100%"
height="500"
frameborder="0"
allow="camera; microphone; autoplay; encrypted-media;"
></iframe>
For iframe-only embeds, you may not need script-src https://embed.typeform.com at all.
CSP for Typeform using nonces
If your site already uses nonces, keep using them. Don’t fall back to 'unsafe-inline' for scripts just because a vendor embed is involved.
Example:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-r4nd0m123' https://embed.typeform.com;
frame-src https://form.typeform.com;
style-src 'self' 'unsafe-inline';
connect-src 'self' https://embed.typeform.com https://form.typeform.com;
img-src 'self' data: https:;
object-src 'none';
base-uri 'self';
HTML:
<div data-tf-widget="AbCdEf12" data-tf-inline-on-mobile></div>
<script nonce="r4nd0m123" src="https://embed.typeform.com/next/embed.js"></script>
If your policy uses 'strict-dynamic', make sure you actually understand how trust propagates. I’ve seen teams copy a nonce-based policy from somewhere else, add third-party scripts, and then wonder why the browser behavior changed. Don’t cargo-cult CSP.
If Typeform still breaks, add these carefully
Depending on embed mode and browser behavior, you may need to widen a couple of directives.
More permissive Typeform policy
Content-Security-Policy:
default-src 'self';
script-src 'self' https://embed.typeform.com;
frame-src 'self' https://form.typeform.com https://embed.typeform.com;
connect-src 'self' https://form.typeform.com https://embed.typeform.com;
style-src 'self' 'unsafe-inline' https://embed.typeform.com;
img-src 'self' data: https:;
font-src 'self' data: https://embed.typeform.com;
object-src 'none';
base-uri 'self';
form-action 'self';
I still wouldn’t use wildcards unless I had hard evidence they were needed.
SurveyMonkey CSP example
SurveyMonkey embeds typically need script and frame access to their own domains.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://widget.surveymonkey.com;
frame-src https://www.surveymonkey.com https://widget.surveymonkey.com;
connect-src 'self' https://www.surveymonkey.com https://widget.surveymonkey.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
object-src 'none';
base-uri 'self';
Example embed:
<script>
(function(t,e,s,n){
var o,a,c;
t.SMCX=t.SMCX||[],e.getElementById(n)||(o=e.getElementsByTagName(s),
a=o[o.length-1],c=e.createElement(s),c.type="text/javascript",c.async=!0,
c.id=n,c.src="https://widget.surveymonkey.com/collect/website/js/tRaiETqnLgj758hTBazgd1_2Bexample.js",
a.parentNode.insertBefore(c,a))
})(window,document,"script","smcx-sdk");
</script>
If you run this inline bootstrap, you’ll need either:
'unsafe-inline'inscript-src— not my favorite- a hash
- a nonce
Nonce version:
script-src 'self' 'nonce-r4nd0m123' https://widget.surveymonkey.com;
<script nonce="r4nd0m123">
(function(t,e,s,n){
var o,a,c;
t.SMCX=t.SMCX||[],e.getElementById(n)||(o=e.getElementsByTagName(s),
a=o[o.length-1],c=e.createElement(s),c.type="text/javascript",c.async=!0,
c.id=n,c.src="https://widget.surveymonkey.com/collect/website/js/tRaiETqnLgj758hTBazgd1_2Bexample.js",
a.parentNode.insertBefore(c,a))
})(window,document,"script","smcx-sdk");
</script>
Google Forms CSP example
Google Forms is usually simpler because it’s often just an iframe.
Content-Security-Policy:
default-src 'self';
frame-src https://docs.google.com;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
object-src 'none';
base-uri 'self';
Embed:
<iframe
src="https://docs.google.com/forms/d/e/1FAIpQLScExample/viewform?embedded=true"
width="640"
height="800"
frameborder="0"
marginheight="0"
marginwidth="0"
>
Loading…
</iframe>
If the page around the form also loads Google assets, you may need script-src, connect-src, or img-src entries for Google domains. Don’t add them unless the browser tells you to.
A realistic CSP when your site already has analytics and consent tooling
Most teams aren’t deploying a page with only a survey embed. There’s usually GTM, Cookiebot, analytics, and app APIs already in play.
Here’s a real-world style header shape based on a production policy from headertest.com, trimmed into something you can adapt for Typeform:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-NDkyNmVhOGUtMjYyOC00Yzc2LWIzM2ItNTQ4MzEyZGQ4OTFk' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com https://embed.typeform.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://u.headertest.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com https://embed.typeform.com https://form.typeform.com;
frame-src 'self' https://consentcdn.cookiebot.com https://form.typeform.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
That’s a much more believable pattern for an actual app than toy examples with three directives.
A couple of opinions here:
object-src 'none'should almost always be present.base-uri 'self'is cheap hardening. Use it.frame-ancestors 'none'is great if nobody should embed your page. If partners need to iframe your page, obviously don’t set that to none.
Common CSP errors and what they actually mean
“Refused to frame … because it violates frame-src”
You forgot the survey host in frame-src.
For Typeform:
frame-src https://form.typeform.com;
“Refused to load the script … because it violates script-src”
The loader SDK is blocked.
For Typeform:
script-src 'self' https://embed.typeform.com;
“Refused to connect to … because it violates connect-src”
XHR, fetch, beacon, or websocket traffic is blocked.
Try:
connect-src 'self' https://embed.typeform.com https://form.typeform.com;
Inline script blocked
Use a nonce or a hash. Don’t knee-jerk to 'unsafe-inline' in script-src.
Report-Only first, then enforce
If you’re adding a survey provider to an existing CSP, ship Content-Security-Policy-Report-Only first.
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' https://embed.typeform.com;
frame-src https://form.typeform.com;
connect-src 'self' https://embed.typeform.com https://form.typeform.com;
report-uri /csp-report;
That lets you see what the embed wants before you break production.
If you support CSP reporting endpoints with report-to, even better. Most teams say they’ll “clean up later” after enabling a wide policy. They don’t.
My recommended baseline for Typeform
If you want one copy-paste policy that’s sane for most Typeform setups, use this and adjust only if the console gives you a concrete reason:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://embed.typeform.com;
frame-src https://form.typeform.com;
connect-src 'self' https://embed.typeform.com https://form.typeform.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'self';
And if the survey is just an iframe, I’d go even tighter:
Content-Security-Policy:
default-src 'self';
frame-src https://form.typeform.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'self';
That’s the real trick with CSP and embeds: start with the smallest thing that can possibly work, then expand with evidence. Not guesses. Not vendor docs written for the lowest common denominator. The browser console is usually the most honest documentation you’ll get.