Web Security: Content Security Policy (CSP) in Depth
One of the most frequent advanced security questions in frontend interviews is:
What is Content Security Policy (CSP)? How do you build a strict CSP directive, and how do nonces and hashes protect against Cross-Site Scripting (XSS)?
Content Security Policy (CSP) is an HTTP response header that defines which resources (scripts, stylesheets, images, connections) the browser is allowed to load and execute for a specific webpage. It acts as a powerful layer of defense to mitigate Cross-Site Scripting (XSS) and data injection attacks.
1. Declarative CSP Directives
The Content-Security-Policy header contains a semi-colon separated list of directives:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trustedscripts.com; img-src 'self' data:;Core Directives:
default-src: Fallback policy for other resource fetch directives when they are not explicitly specified.script-src: Restricts the origins that can execute JavaScript on the page.style-src: Restricts CSS source origins.img-src: Restricts image source origins.connect-src: Restricts the target URLs that client-side JavaScript can request (usingfetch,XMLHttpRequest, orWebSockets).frame-ancestors: Restricts which sites can embed this page inside an<iframe>, preventing Clickjacking attacks.
2. Eliminating 'unsafe-inline' with Nonces and Hashes
By default, strict CSP configurations block inline scripts:
<script>runMaliciousCode();</script> <!-- Blocked by strict CSP! -->Historically, developers resolved this by adding 'unsafe-inline' to the script-src directive, which completely defeats the anti-XSS benefits of CSP. Instead, you should use:
A. Nonces (Number used once)
A nonce is a cryptographically strong, randomly generated token generated by the server on every single page request:
- The server generates a unique token (e.g.
d392a83f982) and adds it to the HTTP CSP header:Content-Security-Policy: script-src 'self' 'nonce-d392a83f982'; - The server injects the matching attribute to valid inline script tags in the HTML document:
<script nonce="d392a83f982">console.log("Safe script run!");</script>
If an attacker injects a script tag without the correct matching nonce token, the browser blocks execution.
B. Hashes
If you have static inline scripts that do not change, you can compute their SHA-256 hash and declare the hash signature inside the CSP header:
- Compute the hash of the exact script content:
console.log("Hello");->sha256-R43... - Define the hash in the CSP header:
Content-Security-Policy: script-src 'self' 'sha256-R43uF5...';
3. CSP Violation Reporting
You can monitor policy violations in production without blocking resources by using the Content-Security-Policy-Report-Only header combined with a report URI destination:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri https://api.my-app.com/csp-report;The browser will send a JSON payload to the report-uri whenever a policy restriction is violated, helping you identify and fix bugs before enforcing strict blocking.
Key Takeaways
- Strict Fallbacks: Always set a restrictive
default-src 'self'fallback policy. - Avoid Unsafe Keywords: Never use
'unsafe-inline'or'unsafe-eval'in production stylesheets or scripts unless absolutely necessary. - Dynamic Nonces: Generate unique nonces per request on server rendering passes; do not use static reusable nonces.
- Report-Only for Staging: Use the
Report-Onlyheader to validate CSP configs before fully rolling out blocking headers.