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-dynamic
  • object-src 'none'
  • frame-ancestors 'none'
  • narrowed form-action and base-uri

But the moment you add VWO, you usually need to expand at least:

  • script-src
  • connect-src
  • img-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:

  1. Route-scoped VWO
  2. Strict carve-out with reporting
  3. Broad allowlist
  4. Global unsafe-inline plus 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.