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 SDKs
  • frame-src — the actual embedded survey iframe
  • connect-src — API calls, analytics, websocket traffic
  • style-src — inline styles or hosted CSS
  • img-src — logos, avatars, tracking pixels
  • font-src — hosted fonts
  • frame-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' in script-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.

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.

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.