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:
- Your app page that contains the iframe
- 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-srccontrols what your page is allowed to embedframe-ancestorscontrols 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-srcon the parentframe-ancestorson 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:
- Does the parent page allow the iframe origin with
frame-src? - Does the Redash response allow your site with
frame-ancestors? - Is there a redirect to another origin?
- 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.