DOM & Browserhard

Design a strict Content Security Policy (CSP) for a SPA

Design a strict CSP for a single-page app dashboard. Cover script/style sources, nonces vs hashes, `strict-dynamic`, reporting, and how to migrate from a permissive policy without breaking production.

Asked at CrowdStrike, Google, Tenable

#security#CSP#XSS#headers

Answer

Interview framing:

  • CSP is defense-in-depth against XSS, data exfiltration, and clickjacking.
  • Goal: prevent inline script execution and limit network egress to a known allowlist.

Strict baseline (recommended starting point):

Content-Security-Policy:
  default-src 'none';
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  style-src  'self' 'nonce-{RANDOM}';
  img-src    'self' data: https://cdn.example.com;
  font-src   'self' data:;
  connect-src 'self' https://api.example.com https://telemetry.example.com;
  frame-ancestors 'none';
  base-uri 'none';
  form-action 'self';
  object-src 'none';
  report-to csp-endpoint;

Key choices:

  • 'nonce-…' + 'strict-dynamic': trust scripts that come from a nonce'd loader, ignore host allowlists. Rotates per response (server-generated, cryptographically random, never reused).
  • 'unsafe-inline' / 'unsafe-eval': never in production. Vite/webpack production builds should not need eval.
  • frame-ancestors 'none' (or specific origins) is the modern X-Frame-Options.
  • object-src 'none' kills Flash/legacy plugin XSS vectors.
  • base-uri 'none' prevents tag injection from rerouting relative URLs.

Migration plan:

  1. Deploy in Report-Only mode (Content-Security-Policy-Report-Only) to a reporting endpoint.
  2. Triage report-to violations by directive; whitelist legitimate sources, fix illegitimate ones.
  3. Replace inline event handlers (onclick) and inline with external + nonce.
  4. Move CSS-in-JS injection through nonce'd tags.
  5. Flip to enforcing mode behind a feature flag, region-by-region.

Common pitfalls:

  • Nonces reused across requests (defeats the purpose).
  • Forgetting connect-src for analytics/websocket endpoints.
  • Missing frame-ancestors lets the page be iframed for clickjacking.
  • 'self' on script-src + a CDN with open uploads = XSS bypass.

Practise more DOM & Browser questions →