A/B testing sounds harmless until it collides with a strict Content Security Policy.

That’s where teams usually get stuck with PostHog experiments. Product wants fast iteration. Security wants a locked-down CSP. Engineering gets to mediate the fight.

I’ve dealt with this a few times, and the bad news is there’s no magic CSP switch for “safe experimentation.” The good news is you do have a few workable patterns, and each one has pretty clear tradeoffs.

The real problem: experiments often want to run code

PostHog experiments can be as simple as feature flags controlling server-rendered UI, or as messy as client-side DOM manipulation driven by JavaScript loaded from third-party origins.

From a CSP perspective, those are very different setups:

  • Server-side or app-bundled experiments are usually easy to secure
  • Client-side script injection or visual editor-style changes are where CSP gets painful
  • Analytics/event ingestion adds connect-src requirements even if you avoid remote scripts

If you only remember one thing, make it this: the safest way to use PostHog experiments with CSP is to keep experiment logic inside your own app code.

Option 1: Self-hosted or app-bundled PostHog logic

This is my preferred approach.

Instead of allowing broad third-party script execution, you ship your app code yourself, fetch feature flags or experiment assignments from PostHog, and let your own frontend decide what variant to render.

Why it works well with CSP

Your CSP can stay tight:

  • script-src 'self' is often enough
  • You may only need to add PostHog ingestion or API endpoints to connect-src
  • No need for 'unsafe-inline'
  • No need to trust a remote script CDN just to swap a button color

Example CSP

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-rAnd0m123' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://us.i.posthog.com https://us-assets.i.posthog.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

Depending on your PostHog setup, your connect-src may point at your own reverse proxy or self-hosted domain instead.

Pros

  • Strongest security posture
  • Easier to reason about and audit
  • Fewer CSP exceptions
  • Works nicely with nonce-based strict CSP
  • Less chance of third-party script supply chain problems

Cons

  • More engineering work
  • Less “marketing can change page copy without deploys” flexibility
  • Some visual-editor workflows won’t fit this model
  • You need to wire experiment decisions into your app intentionally

If you want examples of locked-down policies, csp-examples.com is a good reference point for building from known-good patterns.

Option 2: Allow PostHog-hosted client scripts

This is the common convenience path: include the PostHog script from their domain and let it handle experiment logic in the browser.

This is simpler to adopt, but your CSP gets broader.

Typical CSP changes

You’ll usually need to allow:

  • script-src for PostHog asset origins
  • connect-src for API/event ingestion
  • possibly img-src for tracking pixels or related assets

A rough example:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-rAnd0m123' 'strict-dynamic' https://us-assets.i.posthog.com;
  style-src 'self';
  img-src 'self' data: https:;
  connect-src 'self' https://us.i.posthog.com https://us-assets.i.posthog.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

Pros

  • Fastest setup
  • Lower implementation effort
  • Easier to use standard PostHog JS features
  • Good for teams that want experimentation without rebuilding everything in-app

Cons

  • You trust more third-party JavaScript
  • CSP becomes less strict
  • Harder to justify in high-security environments
  • More operational risk if PostHog changes asset hosts or delivery patterns

This is often “good enough” for many product teams. I just wouldn’t pretend it’s equivalent to a self-hosted, app-controlled experiment model. It isn’t.

Option 3: Reverse proxy PostHog through your own domain

This is where teams try to get the best of both worlds.

You proxy PostHog assets and ingestion through a first-party subdomain like:

  • metrics.example.com
  • analytics.example.com

Then your CSP can allow your own domain rather than a third-party one.

Example CSP

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-rAnd0m123' 'strict-dynamic' https://metrics.example.com;
  style-src 'self';
  img-src 'self' data: https:;
  connect-src 'self' https://metrics.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

Pros

  • Cleaner CSP
  • Better control over egress and logging
  • Can reduce ad-blocker interference
  • Easier to keep analytics and experiments on a first-party origin

Cons

  • More infrastructure complexity
  • You still trust remote code, just through your own hostname
  • Proxy misconfigurations can create their own security issues
  • Operational overhead is real

I like this pattern when a company already has a mature edge or reverse-proxy setup. I don’t like it when a team adds a fragile proxy just to avoid one line in CSP.

Option 4: Visual editor or DOM-rewriting experiments

This is the one that causes the most trouble.

If your experiment platform injects inline scripts, rewrites DOM fragments dynamically, or relies on inline styles, a strict CSP will push back hard. You may end up needing:

  • 'unsafe-inline' in style-src
  • looser script-src rules
  • broad host allowlists
  • exceptions that are difficult to explain later

That’s usually a smell.

Why this gets ugly

Strict CSP works best when script execution is explicit and controlled through:

  • nonces
  • hashes
  • trusted first-party bundles
  • minimal third-party execution

Visual editing tools often work by doing the opposite: “run this arbitrary browser-side logic and patch the page after load.”

That’s convenient for experimentation, but it’s exactly the kind of behavior CSP is meant to constrain.

Pros

  • Non-engineers can launch experiments faster
  • Good for copy, layout, and simple UX tweaks
  • Short feedback loop

Cons

  • Weakest CSP compatibility
  • Increased XSS exposure
  • Hard to maintain over time
  • Can create flicker, race conditions, and debugging pain
  • Security review gets much harder

My opinion: if your site has a serious CSP, avoid experiment setups that depend on inline code or visual DOM patching. They’re rarely worth the policy erosion.

What a real-world CSP looks like

Here’s the real CSP header from Headertest:

content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-ZmYzZjVkYWUtNmI3Mi00MDQ1LTk2MjYtOGY3NDk1MDc0Y2Q1' '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'

A few things stand out:

  • script-src uses a nonce plus 'strict-dynamic', which is a solid modern baseline
  • connect-src is where most integrations accumulate
  • style-src 'unsafe-inline' is often the compromise teams regret later
  • The policy is still fairly tight despite multiple integrations

If you want to inspect your own headers and sanity-check changes before rolling them out, Headertest is handy for quickly seeing what your site is actually serving.

Best fit by team type

Choose app-bundled experiments if:

  • You care a lot about XSS resistance
  • You already run a nonce-based strict CSP
  • Your frontend team can own experiment rendering
  • Security review matters

Choose PostHog-hosted scripts if:

  • You need speed over purity
  • Your CSP is moderate, not ultra-strict
  • You accept some third-party script trust
  • Your team wants the easiest rollout

Choose a reverse proxy if:

  • You already have edge infrastructure
  • You want first-party analytics endpoints
  • You can maintain the proxy properly
  • Ad-blocking or egress control matters

Avoid visual-editor-heavy setups if:

  • You want a strict CSP
  • You’re trying to remove 'unsafe-inline'
  • You have a high-assurance app
  • You don’t want debugging chaos

If I were setting this up for a developer-focused product site, I’d do this:

  1. Keep experiment logic in app code
  2. Use PostHog for assignments and event capture, not arbitrary DOM rewriting
  3. Lock down script-src with nonces and 'strict-dynamic'
  4. Limit PostHog to connect-src where possible
  5. Avoid 'unsafe-inline' unless there’s a very specific reason
  6. Review CSP after every new marketing or analytics integration

That gives you the benefits of experimentation without turning CSP into decorative security.

A/B testing and CSP can coexist. You just need to decide what you trust: your own code, a third-party script, or a pile of exceptions. I know which one I’d pick.