A/B testing tools and strict CSP have a messy relationship. VWO is a good example. Marketing wants fast experiments. Security wants a locked-down policy. Engineering gets stuck in the middle, usually after someone ships unsafe-inline and calls it “temporary.”
I’ve had to clean this up more than once.
If you’re running VWO on a site with a real Content Security Policy, you need to make a choice: loosen CSP enough for VWO to work easily, or keep a stricter policy and accept extra setup, testing, and occasional friction.
Here’s the practical comparison.
The core problem
VWO works by loading third-party JavaScript, modifying the DOM, tracking events, and sometimes injecting styles. That collides with the exact things CSP is designed to control.
A developer-friendly CSP for an app might look like this real-world example from headertest.com:
content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-OGJiMmUxZjMtZjI0MS00N2UxLTk0ZTYtYTg3NTBiYmRlNjQw' '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 some good things:
- nonces on scripts
strict-dynamicobject-src 'none'frame-ancestors 'none'- narrowed
form-actionandbase-uri
But the moment you add VWO, you usually need to expand at least:
script-srcconnect-srcimg-src- sometimes
style-src - sometimes
frame-src
That’s where the tradeoffs start.
Option 1: Allow VWO broadly and move on
This is the approach most teams take first.
A simplified version looks like this:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0m' 'strict-dynamic' https://dev.visualwebsiteoptimizer.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://dev.visualwebsiteoptimizer.com https://*.vwo.com;
frame-src 'self' https://*.vwo.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
You should verify the exact VWO domains you need against the official VWO documentation, because vendors change hostnames over time and features don’t all use the same endpoints.
Pros
- Fastest to implement
- Least likely to break experiments
- Easier for marketing teams to self-serve
- Lower maintenance in the short term
Cons
- Broader trust boundary than most security teams want
style-src 'unsafe-inline'weakens CSP meaningfully- Third-party script gets a lot of power over your page
- Harder to reason about what’s really executing
This is the “make the business happy” option. I don’t love it, but I understand why it exists.
Option 2: Keep a strict CSP and explicitly carve out VWO
This is the better security posture if you can afford the implementation work.
Start from a strict policy and add only the minimum VWO sources you actually observe in production. Something like:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0m' 'strict-dynamic' https://dev.visualwebsiteoptimizer.com;
style-src 'self';
img-src 'self' data: https:;
connect-src 'self' https://dev.visualwebsiteoptimizer.com https://analytics.vwo.com;
frame-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
report-to csp-endpoint;
report-uri /csp-report;
Then you test actual experiments and collect CSP violations in report-only mode first.
Pros
- Better containment if VWO or a dependent script misbehaves
- Easier to justify during security review
- Cleaner long-term policy
- Less accidental trust creep from “just add another wildcard”
Cons
- More setup and validation work
- New VWO features may break until CSP is updated
- Marketing teams may see delays
- You need reporting and someone to actually read it
This is the approach I prefer for developer-owned sites and products with real security requirements.
Option 3: Use unsafe-inline for styles to reduce friction
A lot of A/B testing platforms inject style changes. VWO experiments often need CSS changes, and strict style-src rules can become a headache fast. So teams end up here:
style-src 'self' 'unsafe-inline' https://dev.visualwebsiteoptimizer.com;
That’s also visible in the headertest.com example, where style-src already includes 'unsafe-inline'.
Why teams do this
Because it works. That’s the honest answer.
You can spend hours trying to support dynamic style mutations without opening things up, and eventually someone asks whether the risk is worth the engineering time.
Pros
- Reduces experiment breakage
- Easier for non-engineers to create test variations
- Lower operational overhead
Cons
- Weakens one of CSP’s useful protections
- Makes inline CSS injection possible
- Normalizes a bad exception that tends to spread
If your site is mostly marketing pages and low-risk content, this may be acceptable. If you’re protecting authenticated flows, admin panels, billing pages, or anything handling sensitive data, I’d avoid running VWO there at all, or at least avoid this style policy there.
Option 4: Scope VWO to only the pages that need experiments
This is the most sane compromise, and I wish more teams used it.
Don’t put VWO on the whole site. Don’t give every route the same CSP. Apply a looser policy only to experimentable pages, usually public landing pages or marketing pages.
For example:
Strict policy for app pages
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0m' 'strict-dynamic';
style-src 'self';
img-src 'self' data:;
connect-src 'self' https://api.example.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
Looser policy for marketing pages with VWO
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0m' 'strict-dynamic' https://dev.visualwebsiteoptimizer.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://dev.visualwebsiteoptimizer.com https://*.vwo.com;
frame-src 'self' https://*.vwo.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
Pros
- Best balance of business needs and security
- Limits risk to lower-sensitivity pages
- Keeps app/admin/account areas clean
- Easier to explain to auditors and internal security
Cons
- Needs route-level or app-level CSP control
- More operational complexity
- Teams must be disciplined about where VWO is allowed
If your stack supports per-route headers, this is usually the right answer.
Comparison table
Broad VWO allowlist
Good for: fast rollout, low-friction marketing
Bad for: high-security apps
Pros
- easy
- fewer breakages
- less back-and-forth with non-engineering teams
Cons
- broad third-party trust
- likely style relaxation
- hard to keep tidy
Strict VWO carve-out
Good for: security-conscious teams
Bad for: teams with no CSP reporting or no time to maintain it
Pros
- tighter control
- cleaner policy
- better audit story
Cons
- more maintenance
- more testing required
- easier to break experiments
unsafe-inline styles
Good for: design-heavy experiments on public pages
Bad for: sensitive authenticated surfaces
Pros
- practical
- reduces support burden
- works with many DOM/style mutations
Cons
- weaker CSP
- easier injection path
- tends to become permanent
Route-scoped VWO
Good for: most mature teams
Bad for: stacks with one global header and no routing flexibility
Pros
- strongest compromise
- contains risk
- preserves strict CSP where it matters
Cons
- more moving parts
- needs process discipline
- not always easy on legacy platforms
What I’d recommend
For a developer audience, I’d rank the approaches like this:
- Route-scoped VWO
- Strict carve-out with reporting
- Broad allowlist
- Global
unsafe-inlineplus broad VWO access everywhere
That last one is the classic “we’ll clean it up later” trap. Later rarely comes.
Practical rollout plan
If you’re adding VWO to an existing strict policy, do it in phases:
1. Start with Report-Only
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'nonce-rAnd0m' 'strict-dynamic' https://dev.visualwebsiteoptimizer.com;
style-src 'self';
img-src 'self' data: https:;
connect-src 'self' https://dev.visualwebsiteoptimizer.com https://*.vwo.com;
frame-src 'self' https://*.vwo.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
report-uri /csp-report;
Watch violations during real experiments, not just page load.
2. Separate marketing from product pages
This one change solves a lot of arguments before they start.
3. Avoid wildcards unless you’ve proven you need them
https://*.vwo.com is convenient. It’s also lazy if only one or two hosts are actually required.
4. Be honest about unsafe-inline
If you need it for VWO styles, document that exception and keep it away from high-risk pages.
5. Re-test whenever VWO setup changes
New integrations, new experiment types, tag manager changes, or consent tooling can all shift CSP requirements.
Example: extending a real policy like headertest.com
Using the headertest.com policy as a base, a cautious VWO-compatible version might look like this:
content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-OGJiMmUxZjMtZjI0MS00N2UxLTk0ZTYtYTg3NTBiYmRlNjQw' 'strict-dynamic'
https://www.googletagmanager.com
https://*.cookiebot.com
https://*.google-analytics.com
https://dev.visualwebsiteoptimizer.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
https://dev.visualwebsiteoptimizer.com
https://*.vwo.com;
frame-src 'self'
https://consentcdn.cookiebot.com
https://*.vwo.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
That’s not a final policy. It’s a starting point for testing. You should trim any VWO hosts you don’t need after reviewing actual violations and network activity.
If you want ready-to-use CSP patterns for strict and relaxed setups, csp-examples.com is a handy reference. For vendor-specific hostnames and current integration requirements, check the official VWO documentation before freezing your policy.
My opinion: if VWO is only there to optimize marketing pages, keep it off the rest of the site. That gives marketing room to move without turning your entire CSP into a list of exceptions.