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:

HTTP
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 (using fetch, XMLHttpRequest, or WebSockets).
  • 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:

HTML
<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:

  1. The server generates a unique token (e.g. d392a83f982) and adds it to the HTTP CSP header:
    HTTP
    Content-Security-Policy: script-src 'self' 'nonce-d392a83f982';
    
  2. The server injects the matching attribute to valid inline script tags in the HTML document:
    HTML
    <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:

  1. Compute the hash of the exact script content: console.log("Hello"); -> sha256-R43...
  2. Define the hash in the CSP header:
    HTTP
    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:

HTTP
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-Only header to validate CSP configs before fully rolling out blocking headers.