Skip to content
apertio

Learn

A/B testing without weakening your Content Security Policy

Client-side testing tools make you whitelist third-party scripts and sometimes enable unsafe-eval. Apertio injects no JavaScript and mutates no DOM, so a strict nonce-based CSP stays intact with nothing to add to your policy.

Client-side A/B testing tools make you weaken your Content Security Policy. They inject third-party JavaScript that mutates the live DOM, so you have to whitelist their script origins, and some features need unsafe-eval. Every relaxation you add for a testing tool is a relaxation an attacker can use too.

Apertio injects no JavaScript and mutates no DOM. A variant is data: a primitive or an enum key, resolved server-side or at the edge and rendered by your own components. There is nothing third-party to whitelist, so a strict nonce-based CSP with no unsafe-inline and no unsafe-eval stays intact with Apertio installed.

Why a bounded payload keeps the policy strict

The reason this holds is the variant payload is bounded by construction. A payload is a primitive or an enum key. Never markup, never an arbitrary URL, never a CSS or JS string. Anything that maps to "code," such as which layout renders, is an enum key resolved against a registry the engineer defined. The data carries only the key, and your app maps the key to its own component.

Because every payload is bounded, every possible rendered output is enumerable in advance. That is what makes a pre-launch audit real: the set of outputs is finite and known, not whatever a remote script decided to do this morning.

The payload is a key, never markup

ts
// A bounded enum slot. The payload is a key, never markup, never a URL, never CSS.
import { slot } from '@apertio/core'

export const slots = {
  'home.hero.layout': slot.enum({
    label: 'Hero layout',
    // The app maps each key to a pre-built component it already ships.
    // The variant data carries 'split', never the component or any markup.
    options: ['centered', 'split', 'media-right'] as const,
    default: 'centered',
  }),
}
ts
// The app renders the key with its own components. No injected JS, no DOM mutation.
// useSlot is created once via createApertio(slots) and imported locally.
import { useSlot } from '../apertio'

const layouts = {
  centered: CenteredHero,
  split: SplitHero,
  'media-right': MediaRightHero,
}

export function Hero() {
  const layout = useSlot('home.hero.layout') // 'centered' | 'split' | 'media-right'
  const Component = layouts[layout]
  return <Component />
}

The app maps the key to a component it already ships. No injected JS.

Nothing to whitelist

No third-party script origin enters your policy because Apertio injects no script. A nonce-based CSP with no unsafe-inline and no unsafe-eval stays as it is.

The deployed code is the authority

The schema compiled into your running app is the boundary on what can render. Even a compromised platform or a stolen token cannot make the app render out of bounds.

Re-validated at resolution

The SDK re-checks every value against the embedded schema when it resolves, then clamps or falls back to the default. This is the one check a compromised control plane cannot bypass.

Serialized safely

The server-rendered resolved-slots payload is JSON-encoded so it cannot break out of its context. No inline handler injection, so the strict policy holds end to end.

The embedded schema is the authority

The embedded schema being the authority is the part that survives a bad day. The platform stores and serves variant values, but it is an editing mirror. If the platform is compromised or an API token is stolen, the worst an attacker can push is a value that the embedded schema rejects at resolution, at which point the SDK falls back to the slot’s default and logs a suppressed exposure for observability.

Two honest boundaries. Type-valid is not content-safe: a bounded variant can still carry a misleading headline, which is a governance problem, not a CSP one. And only modeled slots are testable, so a new surface still needs an engineering change. Neither boundary requires loosening your policy.

Keep the policy you have

Read how the embedded schema stays the authority on what can render.

Frequently asked questions

Does Apertio require changes to my Content Security Policy?
No. Apertio injects no third-party JavaScript and mutates no DOM, so there is nothing to whitelist. A strict nonce-based CSP with no unsafe-inline and no unsafe-eval stays intact with Apertio installed.
Why do other A/B testing tools force me to weaken CSP?
Client-side tools inject third-party JavaScript that mutates the live DOM, so you have to whitelist their script origins, and some features need unsafe-eval. Each relaxation you add for the tool is one an attacker can use too.
Does Apertio need unsafe-eval or unsafe-inline?
No. Variants resolve server-side or at the edge as bounded data and render through your own components. The server-rendered payload is JSON-encoded so it cannot break out of its context, so neither unsafe-eval nor unsafe-inline is needed.
What stops a compromised platform from rendering something malicious?
The schema compiled into your deployed app is the authority. The SDK re-validates every value against that embedded schema at resolution and clamps or falls back to the default, so a stolen token or compromised control plane cannot make the app render out of bounds.