CSP Nonces and Hashes for Inline JavaScript

Content Security Policy (CSP) protects web applications from Cross-Site Scripting (XSS) attacks by restricting the execution of untrusted scripts. By default, a robust CSP blocks all inline JavaScript because browsers cannot distinguish between legitimate inline code and malicious injections. CSP nonces and hashes resolve this challenge by providing cryptographic mechanisms to whitelist and execute specific, trusted inline scripts without resorting to insecure directives like 'unsafe-inline'.

The Security Problem with Inline Scripts

Inline scripts—code embedded directly within HTML tags like <script> or event handlers—are a primary vector for XSS attacks. If an attacker injects a script payload into the DOM, the browser automatically executes it unless instructed otherwise. A strict CSP disables all inline execution by default, which enhances security but can break legitimate application functionality that relies on inline code.

The Role of CSP Nonces

A nonce (a cryptographic “number used once”) is a random, cryptographically secure base64-encoded token generated dynamically by the web server for each unique HTTP response.

To use a nonce: 1. The server generates a unique nonce value per request. 2. The server sends this value in the CSP HTTP header: Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'. 3. The server applies the identical nonce attribute to trusted inline script tags: <script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">...</script>.

When rendering the page, the browser compares the script tag’s nonce attribute with the nonce value in the CSP header. If they match, the browser executes the script; if they do not match, or if the attribute is missing, execution is blocked. Because attackers cannot predict the dynamic nonce for a future request, injected scripts cannot execute.

The Role of CSP Hashes

A CSP hash permits specific inline scripts based on their exact cryptographic content rather than a dynamic token. Instead of generating a new value per request, the developer or build tool generates a cryptographic hash (using SHA-256, SHA-384, or SHA-512) of the script’s exact contents.

To use a hash: 1. Calculate the digest of the script text: sha256-<base64-value>. 2. Add the digest directly to the CSP header: Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZPDB_DC8='. 3. Place the script inside standard <script> tags without any special attributes.

When the browser parses the inline script, it computes the script’s hash and checks it against the CSP header. If the hash matches, the script runs. If even a single character or whitespace within the script changes, the calculated hash changes, and the browser refuses to execute it.

Nonces vs. Hashes: When to Use Each

By leveraging nonces and hashes, developers can maintain strict CSP rules that block unauthorized script execution while retaining the ability to run necessary inline JavaScript securely.