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
- Use Nonces for Dynamic Content: Nonces are best suited for server-rendered applications (SSR) where the backend can generate and inject unique tokens into HTML templates on every request.
- Use Hashes for Static Content: Hashes are ideal for static websites, single-page applications (SPAs), or content delivered via CDNs where generating a per-request dynamic nonce is not feasible.
- Maintenance Differences: Nonces require backend infrastructure to generate cryptographically secure strings per request. Hashes require updating the CSP header whenever the inline script content is modified.
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.