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-srcrequirements 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-srcfor PostHog asset originsconnect-srcfor API/event ingestion- possibly
img-srcfor 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.comanalytics.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'instyle-src- looser
script-srcrules - 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-srcuses a nonce plus'strict-dynamic', which is a solid modern baselineconnect-srcis where most integrations accumulatestyle-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
My recommended baseline
If I were setting this up for a developer-focused product site, I’d do this:
- Keep experiment logic in app code
- Use PostHog for assignments and event capture, not arbitrary DOM rewriting
- Lock down
script-srcwith nonces and'strict-dynamic' - Limit PostHog to
connect-srcwhere possible - Avoid
'unsafe-inline'unless there’s a very specific reason - 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.