Strict Content Security Policy (CSP) in 2026: Nonces, Hashes & DOM-XSS Trusted Types

A comprehensive web security engineering guide to implementing Strict Content Security Policy (CSP) in 2026: nonce-based script execution, 'strict-dynamic', W3C Trusted Types, and Next.js middleware.
Strict Content Security Policy (CSP) in 2026: Nonces, Hashes & DOM-XSS Trusted Types
For years, software engineering teams attempted to secure web applications by defining complex Domain Allowlist Content Security Policies (CSPs):
Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://apis.google.com;In production reality, Domain Allowlist CSPs are virtually useless:
- Attackers easily bypass domain allowlists by hosting malicious scripts on open CDN endpoints (like JSONP endpoints on
apis.google.comor vulnerable libraries oncdnjs). - Maintaining hundreds of third-party domains in a CSP header becomes an unmaintainable operational nightmare.
- Domain allowlists provide zero protection against DOM-based XSS, where malicious data is passed into dangerous client-side JavaScript sinks like
element.innerHTML = userInput.
In 2026, web security standards have converged on the Strict Nonce-Based CSP + W3C Trusted Types Architecture.
Recommended by Google, OWASP, and Microsoft, a Strict CSP eliminates domain whitelists entirely, allowing scripts to execute only if they carry a cryptographically random, per-request server nonce while using Trusted Types to lock down the browser's internal DOM parser.
In this deep cybersecurity engineering guide, we walk through how to build, deploy, and verify a Strict CSP in Next.js (App Router) and Express based on secure production architectures engineered at MojoStudio.
1. The 2026 Strict CSP Blueprint
+-----------------------------------------------------------------------------------------+
| The Strict Nonce-Based CSP + Trusted Types Architecture |
+-----------------------------------------------------------------------------------------+
[HTTP Request arrives at Next.js / Express Server]
|
v
+-----------------------------------------------------------------+
| 1. Generate 32-Byte Cryptographic Nonce: 'nonce-rAnd0m123==' |
| 2. Set Response Header: |
| Content-Security-Policy: |
| object-src 'none'; |
| base-uri 'none'; |
| script-src 'nonce-rAnd0m123==' 'strict-dynamic'; |
| require-trusted-types-for 'script'; |
+-----------------------------------------------------------------+
|
v
[Browser Engine Evaluates Scripts on Page]
- <script nonce="nonce-rAnd0m123==" src="/app.js"> ---> [ALLOWED & EXECUTED!]
- <script>alert('Attacker XSS!')</script> ----------> [BLOCKED! (Missing valid nonce)]
- element.innerHTML = "raw_string" -----------------> [BLOCKED! (Violates Trusted Types)]The 4 Pillars of a Strict CSP:
object-src 'none';: Completely disables obsolete, dangerous browser plugins like Flash and Java Applets.base-uri 'none';: Prevents attackers from injecting<base href="https://evil.com">tags that hijack relative script URLs.script-src 'nonce-{RANDOM}' 'strict-dynamic';: Allows only scripts bearing the exact matching cryptographic nonce.'strict-dynamic': Allows trusted scripts (with a valid nonce) to dynamically load downstream dependencies (like Google Analytics or Stripe SDK) without manually adding them to the CSP header!
2. Production Implementation in Next.js (App Router & Middleware)
Because the CSP nonce must be unique on every single HTTP request, you cannot configure a static CSP header in next.config.js.
The nonce must be generated dynamically inside middleware.ts:
// middleware.ts (Next.js 15+ App Router)
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
// 1. Generate a cryptographically random 128-bit Base64 Nonce
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
// 2. Define the Strict CSP Policy
const cspHeader = `
default-src 'self';
script-src 'nonce-${nonce}' 'strict-dynamic' https: 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https:;
font-src 'self';
object-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'none';
require-trusted-types-for 'script';
trusted-types default nextjs_policy;
upgrade-insecure-requests;
`.replace(/\s{2,}/g, " ").trim();
// 3. Set Request Headers to pass nonce to React Server Components
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", cspHeader);
// 4. Set Response Headers for the Browser
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
response.headers.set("Content-Security-Policy", cspHeader);
return response;
}
export const config = {
matcher: [
// Apply to all application routes except static assets & favicons
{
source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
missing: [
{ type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" },
],
},
],
};Passing the Nonce to React Components (app/layout.tsx):
// app/layout.tsx
import { headers } from "next/headers";
import Script from "next/script";
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const headersList = await headers();
const nonce = headersList.get("x-nonce") || "";
return (
<html lang="en">
<body>
{children}
{/* Next.js automatically injects the cryptographic nonce into script tags! */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XYZ"
strategy="afterInteractive"
nonce={nonce}
/>
</body>
</html>
);
}3. W3C Trusted Types: Eliminating DOM-Based XSS
Even with a Strict CSP, an application can still be vulnerable to DOM XSS if frontend code takes untrusted URL parameters and inserts them into the document:
// VULNERABLE CLIENT-SIDE JAVASCRIPT
const query = new URLSearchParams(window.location.search).get("search");
document.getElementById("search-output").innerHTML = query; // DOM XSS!How Trusted Types Stops This:
When you add require-trusted-types-for 'script' to your CSP, the browser physically refuses to accept raw strings in any DOM sink:
// Browser throws native Uncaught TypeError:
// "Failed to set the 'innerHTML' property on 'Element': This document requires 'TrustedHTML' assignment."Registering a Trusted Types Sanitization Policy with DOMPurify:
import DOMPurify from "dompurify";
// Register default Trusted Types policy
if (window.trustedTypes && window.trustedTypes.createPolicy) {
window.trustedTypes.createPolicy("default", {
createHTML: (stringInput: string) => {
// Automatically sanitize all HTML inputs through DOMPurify!
return DOMPurify.sanitize(stringInput, { RETURN_TRUSTED_TYPE: false });
},
createScriptURL: (urlInput: string) => {
// Enforce whitelist for dynamically loaded scripts
if (urlInput.startsWith("https://trusted-cdn.mojostudio.in/")) {
return urlInput;
}
throw new Error(`Untrusted script URL blocked: ${urlInput}`);
},
createScript: (scriptInput: string) => {
throw new Error("Inline dynamic scripts via eval are strictly forbidden!");
},
});
}4. Safe Deployment: The Report-Only Rollout Strategy
Deploying a strict CSP directly into production can accidentally break third-party marketing tags or analytics pixels.
Enterprise teams always deploy using Content-Security-Policy-Report-Only for the first 14 days:
Content-Security-Policy-Report-Only: script-src 'nonce-XYZ' 'strict-dynamic'; report-uri /api/csp-violations;- The browser executes all scripts normally (zero broken features).
- If a script violates the policy, the browser dispatches a JSON telemetry report to
/api/csp-violations. - Engineers inspect violation logs in Datadog/Grafana, attach nonces to legitimate scripts, and switch to enforcing
Content-Security-Policyonce violation count drops to zero.
Conclusion: Total Browser Defense in 2026
Content Security Policy has evolved from a fragile maintenance headache into the most powerful browser security defense in web engineering.
By replacing brittle domain allowlists with cryptographic per-request nonces, leveraging 'strict-dynamic' for modern script loading, and locking down DOM injection sinks with W3C Trusted Types, engineering teams eliminate over 95% of all Cross-Site Scripting vulnerabilities at the browser engine level.
At MojoStudio, our frontend security engineers design enterprise Content Security Policies, Trusted Types sanitization frameworks, and automated violation monitoring pipelines. Contact our cybersecurity team to audit and lock down your web application today.
Frequently Asked Questions
1. What is a Strict Content Security Policy (CSP)?
A Strict CSP is a modern security policy that relies on cryptographically random, per-request nonces (nonce-{random}) and the 'strict-dynamic' directive to restrict script execution, replacing legacy and easily bypassed domain name allowlists.
2. What does the 'strict-dynamic' directive do?
'strict-dynamic' specifies that the trust granted to a script carrying a valid cryptographic nonce is automatically propagated to any downstream scripts dynamically loaded by that root script, eliminating the need to allowlist third-party domains.
3. Why are domain allowlist CSPs considered obsolete?
Domain allowlists are fragile and vulnerable to bypasses: attackers can abuse open CDNs (like cdnjs or Google APIs) hosted on allowlisted domains to execute arbitrary malicious scripts.
4. What are W3C Trusted Types?
Trusted Types is a browser security mechanism enforced via CSP that prevents DOM-based XSS by requiring all data passed into dangerous sinks (like innerHTML or document.write) to be wrapped in typed TrustedHTML objects created by sanitization policies.
5. Why must a CSP Nonce be generated per request?
A nonce must be a cryptographically random, unguessable string generated freshly on every HTTP request. If a nonce is static, an attacker can simply hardcode the known nonce into their XSS payload.
6. How do you implement a dynamic CSP in Next.js?
In Next.js App Router, dynamic CSP nonces are generated inside middleware.ts using crypto.randomUUID(), set in the response headers, and forwarded to React Server Components via custom request headers.
7. What is the difference between Content-Security-Policy and Content-Security-Policy-Report-Only?
Content-Security-Policy blocks violating scripts immediately. Content-Security-Policy-Report-Only allows violating scripts to run while sending violation telemetry reports to a specified endpoint, ideal for safe production testing.
8. Why is frame-ancestors 'none' important in CSP?
The frame-ancestors 'none' directive prevents your website from being embedded inside <iframe> tags on external malicious websites, completely eliminating Clickjacking attacks (superseding legacy X-Frame-Options: DENY).
9. How does DOMPurify integrate with Trusted Types?
DOMPurify natively supports Trusted Types, allowing you to configure it as the default sanitization policy so any string assigned to innerHTML is automatically sanitized and converted to TrustedHTML.
10. How does MojoStudio help companies deploy Strict CSP?
MojoStudio engineers custom Next.js and Express CSP middlewares, nonced script loaders, Trusted Types DOM policies, and automated CSP violation monitoring dashboards. Explore our Backend & Web Services to learn more.
Frequently Asked Questions
A Strict CSP is a modern security policy that relies on cryptographically random, per-request nonces (`nonce-{random}`) and the `'strict-dynamic'` directive to restrict script execution, replacing legacy and easily bypassed domain name allowlists.