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
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:
- Deploy in Report-Only mode (
Content-Security-Policy-Report-Only) to a reporting endpoint. - Triage report-to violations by directive; whitelist legitimate sources, fix illegitimate ones.
- Replace inline event handlers (onclick) and inline with external + nonce.
- Move CSS-in-JS injection through nonce'd tags.
- 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.