Redash embeds look simple: drop an <iframe> on the page and move on. Then CSP gets involved and suddenly the dashboard is blank, the browser console is yelling, and somebody suggests adding * everywhere.

Don’t do that.

When Redash embeds fail under CSP, the root cause is usually boring: the wrong directive, the wrong origin, or a policy applied on the wrong side of the iframe boundary. I’ve seen teams lose hours because they changed the parent page policy when the real blocker lived on the Redash server, or vice versa.

Here are the mistakes I see most often, and the fixes that actually work.

First: know which page owns which CSP rule

For Redash embeds, there are two different documents involved:

  1. Your app page that contains the iframe
  2. The Redash page loaded inside the iframe

Those two pages can each send their own CSP headers. They don’t share one policy.

That matters because:

  • frame-src controls what your page is allowed to embed
  • frame-ancestors controls who is allowed to embed a page

That distinction trips people up constantly.

Mistake #1: Setting frame-ancestors on the parent page

I see this one a lot. Someone wants to allow their app to embed Redash, so they add this to the parent app:

Content-Security-Policy: frame-ancestors 'self' https://redash.example.com

That does nothing for embedding Redash.

frame-ancestors is enforced by the page being framed, not the page doing the framing. If Redash sends frame-ancestors 'none', your parent page cannot override that.

A real-world example from a production header looks like this:

content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-ZjcwNjg3OWItZDM5MC00ZTRmLTkzNzYtYjgwZDRiMzIxMTE0' '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 Redash responded with frame-ancestors 'none', embedding would be dead on arrival no matter what your app allows.

Fix

Configure the Redash response to allow your site as an ancestor:

Content-Security-Policy: frame-ancestors 'self' https://app.example.com

If you have multiple environments:

Content-Security-Policy: frame-ancestors 'self' https://app.example.com https://staging.example.com

Avoid broad patterns unless you really need them.

Official docs: MDN CSP frame-ancestors

Mistake #2: Forgetting frame-src on the parent page

This is the other half of the same problem. Your app page must explicitly allow the Redash origin in frame-src or a fallback directive.

Broken policy:

Content-Security-Policy: default-src 'self'

With that policy, this embed is blocked:

<iframe
  src="https://redash.example.com/public/dashboards/abc123"
  width="100%"
  height="800"
  loading="lazy">
</iframe>

Fix

Allow the Redash origin in frame-src:

Content-Security-Policy: default-src 'self'; frame-src 'self' https://redash.example.com

If you’re generating policies in code:

const csp = [
  "default-src 'self'",
  "frame-src 'self' https://redash.example.com",
  "object-src 'none'",
  "base-uri 'self'"
].join('; ');

If you want ready-made patterns for iframe policies, csp-examples.com is handy.

Mistake #3: Allowing the wrong origin

This sounds obvious, but it’s extremely common with Redash because teams mix up:

  • app origin
  • Redash UI origin
  • API origin
  • CDN origin
  • SSO/login origin

If your iframe loads from:

<iframe src="https://analytics.example.com/public/dashboards/abc123"></iframe>

then frame-src must allow https://analytics.example.com, not https://example.com and not https://api.example.com.

Scheme and subdomain matter.

Bad:

frame-src https://example.com

Good:

frame-src https://analytics.example.com

Also watch for redirects. If your iframe URL 302s to a login domain or a different host, the final framed resource must also be allowed and must allow being framed.

Fix

Check the browser devtools network tab and confirm the final response origin. Don’t guess.

Mistake #4: Trying to fix iframe problems with script-src

A lot of CSP debugging turns into random editing. Somebody sees a blank embed and starts adding domains to script-src, style-src, img-src, everything except the directive that actually matters.

For initial iframe loading, the first directive to check is almost always:

  • frame-src on the parent
  • frame-ancestors on Redash

Not script-src.

The header example above is a good reminder that a policy can be very detailed about scripts and still block framing with one line:

frame-ancestors 'none'

Fix

Debug in this order:

  1. Does the parent page allow the iframe origin with frame-src?
  2. Does the Redash response allow your site with frame-ancestors?
  3. Is there a redirect to another origin?
  4. Only then look at nested asset loads like scripts, images, fonts, or XHR/fetch.

Mistake #5: Forgetting connect-src for embedded app behavior

A plain public Redash dashboard may render fine in an iframe, but more interactive setups can still break if the embedded page needs API calls, websockets, or background fetches.

That’s where connect-src shows up.

The sample header above includes:

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

That pattern is common: the page itself loads, but dynamic data, tracking, or realtime features fail because connect-src is too tight.

Fix

If your Redash page makes network requests back to its own backend or websocket endpoints, allow them in the CSP served by Redash:

Content-Security-Policy:
  default-src 'self';
  connect-src 'self' https://analytics.example.com wss://analytics.example.com;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  script-src 'self';
  frame-ancestors https://app.example.com;

Don’t add https: to connect-src just to make the error disappear. That’s lazy and usually wider than you need.

Official docs: MDN CSP connect-src

Mistake #6: Using default-src and assuming it covers everything cleanly

Yes, default-src is a fallback for some fetch directives. No, that does not make it a good place to stop.

People write:

Content-Security-Policy: default-src 'self' https://analytics.example.com

Then months later they can’t tell whether framing was allowed intentionally or by fallback, and tightening the policy becomes risky.

Fix

Be explicit for iframe use cases:

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://analytics.example.com;
  object-src 'none';
  base-uri 'self';

Readable policies are easier to maintain and much easier to debug.

Mistake #7: Leaving frame-ancestors too strict in non-prod

I’ve seen Redash embeds work in production and fail in staging because the Redash CSP only allowed the prod app domain.

Example:

frame-ancestors https://app.example.com

But the staging app lives at:

https://staging-app.example.com

Result: staging iframe blocked, production fine, everyone confused.

Fix

List every legitimate embedding origin:

frame-ancestors https://app.example.com https://staging-app.example.com http://localhost:3000

For local development, you may need localhost temporarily. Just don’t accidentally ship that to production.

Mistake #8: Ignoring console errors and debugging blind

Browsers are usually pretty clear about CSP failures. The console will tell you whether it was:

  • refused to frame
  • refused to load frame
  • refused to connect
  • refused to load script/style/image

Yet plenty of teams still debug by trial and error.

Fix

Open devtools and read the exact violation message. A few examples:

Parent blocked iframe load:

Refused to frame 'https://analytics.example.com/' because it violates the following Content Security Policy directive: "frame-src 'self'".

Embedded page blocked being framed:

Refused to frame 'https://analytics.example.com/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self'".

Those are different problems with different fixes.

A sane baseline for Redash embeds

For the parent app:

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://analytics.example.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';

For the Redash side:

Content-Security-Policy:
  default-src 'self';
  connect-src 'self' https://analytics.example.com wss://analytics.example.com;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  script-src 'self';
  frame-ancestors https://app.example.com https://staging-app.example.com;
  base-uri 'self';
  object-src 'none';

You may need more depending on your Redash setup, auth flow, or asset hosting, but this is a clean place to start.

The rule I keep coming back to

When Redash embeds break under CSP, don’t widen everything. Figure out which document is enforcing which rule.

If your app can’t load the iframe, fix frame-src on the app.

If Redash refuses to be embedded, fix frame-ancestors on Redash.

If the iframe loads but data or live features fail, check connect-src.

That mental model saves a lot of time, and more importantly, it keeps you from turning CSP into decorative security.