If you embed Balsamiq mockups on a site with a real Content Security Policy, you hit the usual tradeoff fast: keep the policy tight, or make the embed work with the least drama.
I’ve dealt with this enough times that I now split the problem into a few clear patterns. Balsamiq embeds are usually easiest when treated as a framed third-party app. The hard part is not Balsamiq itself. The hard part is everything around it: analytics, consent tools, inline styles, legacy scripts, and whatever your framework injects.
So here’s the practical comparison guide for CSP on pages that need Balsamiq embeds.
The baseline: what a real production CSP looks like
A lot of teams start with a policy that already has baggage. A real header from headertest.com looks like this:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-NjBlN2YyOWQtODU4YS00ZmFmLWFhYWYtMTVmYjM0ZWVjZGQ3' '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'
That policy is already doing a few good things:
object-src 'none'base-uri 'self'form-action 'self'frame-ancestors 'none'- nonce-based
script-srcwith'strict-dynamic'
But it also shows the usual compromises:
style-src 'unsafe-inline'- broad
img-src https: - several third-party domains in multiple directives
Adding Balsamiq should not turn this into a wildcard party.
Option 1: Dedicated iframe allowlist for Balsamiq
This is the cleanest option if Balsamiq is embedded with an <iframe> and the parent page does not need to run Balsamiq scripts directly.
Example
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-src 'self' https://*.balsamiq.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
If your exact embed host is more specific, use that instead of https://*.balsamiq.com.
Pros
- Keeps Balsamiq isolated to
frame-src - No need to trust Balsamiq in
script-src,style-src, orconnect-src - Easy to reason about during reviews
- Least likely to weaken the rest of the app
Cons
- Only works if the integration is truly iframe-based
- You need to know the actual Balsamiq embed origin
- If Balsamiq changes subdomains or uses supporting endpoints, you may need to update the policy
My take
If you can make Balsamiq work with just frame-src, do that and stop there. This is the boring option, which usually means it’s the right one.
Option 2: Add Balsamiq to an existing third-party-heavy policy
This is what most teams actually do. They already have GTM, Google Analytics, Cookiebot, maybe a form vendor, and now they want Balsamiq too.
Using the headertest.com policy as a base, the smallest likely change is:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-{RANDOM}' '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 https://*.balsamiq.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
Pros
- Minimal change
- Fastest path in an existing production setup
- Low blast radius if Balsamiq only needs framing
Cons
- Builds on top of an already complex policy
- Makes it harder to tell which third party needs what
- Teams tend to cargo-cult additions forever
My take
This is fine as a short-term move. I just wouldn’t stop here. Once you have five vendors in a policy, nobody remembers why half the entries exist.
Option 3: Split policy by route or page type
If only one page needs Balsamiq, don’t loosen CSP site-wide.
For example:
/app/*gets your stricter application policy/docs/mockups/*gets a policy that allows Balsamiq frames
Example for the Balsamiq page
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic';
style-src 'self';
img-src 'self' data:;
frame-src https://*.balsamiq.com;
frame-ancestors 'none';
base-uri 'self';
object-src 'none';
form-action 'self'
Pros
- Best security boundary for mixed-content sites
- Limits third-party exposure to the page that needs it
- Easier maintenance over time
Cons
- Requires route-level header support
- More moving parts in app config, CDN config, or reverse proxy rules
- Developers sometimes forget to apply the right policy to new pages
My take
I’m a big fan of route-specific CSP. If your platform supports it cleanly, this is the most mature option. Security policies should match page behavior, not wishful thinking.
Option 4: Over-broad fallback policy
This is the “just make it work” version:
Content-Security-Policy:
default-src 'self' https:;
script-src 'self' 'unsafe-inline' 'unsafe-eval' https:;
style-src 'self' 'unsafe-inline' https:;
img-src 'self' data: https:;
connect-src 'self' https: wss:;
frame-src 'self' https:;
object-src 'none'
Pros
- Things usually work quickly
- Lowest integration friction
Cons
- Bad policy
- Very bad policy
- You’ve effectively stopped using CSP as a meaningful control
My take
Don’t do this unless you’re debugging in a temporary environment and you’ve got the discipline to throw it away immediately after. Most teams don’t.
What directives usually matter for Balsamiq embeds
For a normal iframe embed, frame-src is the main one.
Depending on your page and integration style, you may also care about:
frame-srcfor the embedded originimg-srcif your own page loads remote preview assetsconnect-srcif your page fetches Balsamiq-related data directlyscript-srconly if you load Balsamiq JavaScript into the parent pageframe-ancestorsto control who may embed your page
If you want directive-by-directive background, the official CSP reference on MDN Content Security Policy is the first place I’d check. For concise directive explainers, CSP Guide is also useful.
Recommended policy patterns
Best default for a simple Balsamiq iframe page
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-src https://*.balsamiq.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
Best default when you already have third parties
Keep your existing allowlists, but only add Balsamiq where needed:
frame-src 'self' https://consentcdn.cookiebot.com https://*.balsamiq.com;
Not in default-src. Not in script-src. Not everywhere “just in case”.
Common mistakes
Putting Balsamiq in default-src only
That may work as a fallback in some cases, but it’s sloppy. Use the specific directive that matches the resource type. If it’s a frame, use frame-src.
Confusing frame-src and frame-ancestors
I still see this a lot.
frame-srccontrols what your page can embedframe-ancestorscontrols who can embed your page
Very different jobs.
Expanding script-src without evidence
If the embed is an iframe, the parent page usually does not need Balsamiq in script-src. Don’t grant script execution to a third party unless you know why.
Leaving report-only forever
Run a report-only policy while testing if you need to, then enforce it. A report-only CSP that never graduates is just security-themed logging.
My recommendation
If you’re embedding Balsamiq, I’d choose one of these in order:
- Route-specific CSP with Balsamiq only in
frame-src - Site-wide CSP with Balsamiq only in
frame-src - Anything broader only after verifying a real requirement
That’s the whole game: make the embed work with the narrowest permission set possible.
If your current policy looks like the headertest.com example, you’re already dealing with real-world complexity. Don’t make it worse by tossing Balsamiq into every directive. Add the exact origin to frame-src, test in report-only mode, inspect violations, and only widen further if the browser proves you need to.
That approach is slower by maybe ten minutes. It saves a lot more than ten minutes later.