tezvyn:

Deploying a strict CSP for an Express SPA

AI-drafted, machine-checkedSource: interviewadvanced
WHAT IT TESTS

Real CSP rollout without unsafe-inline.

OUTLINE

Define directives, start in Report-Only to gather violations, then enforce; allow inline code via per-request nonces or hashes plus strict-dynamic instead of unsafe-inline.

WHAT THIS TESTS: Whether you can ship a meaningful CSP without breaking the SPA, and whether you know the nonce/hash mechanisms that make unsafe-inline unnecessary.

A GOOD ANSWER COVERS: First define the directives: default-src 'self', and tighten script-src, style-src, img-src, connect-src, font-src to the specific origins the SPA actually uses, plus object-src 'none' and base-uri 'self'. Deploy in two phases. Phase one uses the Content-Security-Policy-Report-Only header with a report-to/report-uri endpoint, so the browser reports what would have been blocked without actually blocking it; you collect violations from real traffic and tune the policy. Phase two flips to the enforcing Content-Security-Policy header once reports are clean. To allow legitimate inline scripts and styles without 'unsafe-inline', use one of two mechanisms: per-request nonces, where the server generates a fresh cryptographically random nonce each request, adds nonce-VALUE to script-src, and stamps the same nonce attribute on each inline tag; or hashes, where you compute the sha256 of the exact inline content and list it in the directive. Adding 'strict-dynamic' lets a nonced loader script transitively load its own dependencies without enumerating every CDN. In Express, Helmet's contentSecurityPolicy middleware sets these headers and supports a per-request nonce via res.locals.

COMMON WRONG ANSWERS: Adding 'unsafe-inline' to make the app work, which negates the protection; using a static nonce reused across requests (predictable, useless); enforcing immediately and breaking production; forgetting connect-src so XHR/fetch and websockets are blocked.

LIKELY FOLLOW-UPS: Why is a reused nonce unsafe? What does 'strict-dynamic' solve? Why deploy Report-Only first? How do you handle third-party widgets and bundler-generated inline code?

ONE CONCRETE EXAMPLE: Express middleware generates a random nonce per request, stores it in res.locals.nonce, sets script-src 'self' 'nonce-abc123' 'strict-dynamic', and the template renders the bootstrap script tag with that nonce. Inline scripts without the matching nonce are blocked, killing injected XSS payloads, while the app's own code runs.

Read the original → developer.mozilla.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.