Web Security: Content Security Policy (CSP)
Cross-Site Scripting (XSS) remains one of the most dangerous vulnerabilities in modern frontend applications. If an attacker manages to inject a malicious <script> tag into your application via an un-sanitized comment field or a compromised 3rd-party npm dependency, the script executes with full access to user session cookies, local storage tokens, and DOM data.
Content Security Policy (CSP) is an HTTP response header that restricts the resource origins (JavaScript, CSS, Images, WebSockets) that the browser is permitted to load and execute. CSP acts as a powerful browser-enforced defense-in-depth safety net: even if an XSS vulnerability exists in your HTML code, a strict CSP prevents the injected script from executing or exfiltrating data to external servers. This guide details CSP directives, nonce generation, 'strict-dynamic', and automated report telemetry.
Mental Model: Unrestricted Client Script Execution vs Strict Content Security Policy
By default, web browsers execute any JavaScript code encountered inside <script> tags or inline event attributes (onload=...), regardless of where the code originated. This trust model makes applications vulnerable to malicious script injection.
Content Security Policy (CSP) establishes an explicit allowlist of trusted script origins and execution rules.
When a server sends the Content-Security-Policy header, the browser inspects every external script fetch, inline block, and WebSocket connection request against policy rules. Any resource violating the policy is blocked instantly before execution. For web security mitigation strategies, review securing web applications owasp top 10 mitigation and securing single page applications oauth2 pkce.
Quick reference
- Browser-enforced HTTP response header restricting script, style, image, and WebSocket origins.
- Neutralizes XSS exploitation by blocking inline scripts and unauthorized external domain fetches.
- Prevents data exfiltration by restricting outbound connect-src API fetch destinations.
- Suppresses dangerous legacy JavaScript APIs like eval() and inline onclick handlers.
- Supported natively across all modern desktop and mobile browser engines.
Remember this
Enforce strict Content Security Policies to neutralize XSS script execution and data exfiltration.
CSP Directives: script-src, style-src, connect-src, & img-src
A comprehensive CSP header configures specialized fetch directives for distinct resource categories:
1Content-Security-Policy: 2 default-src 'self'; 3 script-src 'self' 'nonce-rAnd0mN0nc3' 'strict-dynamic'; 4 style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; 5 font-src 'self' https://fonts.gstatic.com; 6 img-src 'self' data: https://images.unsplash.com; 7 connect-src 'self' https://api.coreconcept.com wss://ws.coreconcept.com; 8 frame-ancestors 'none'; 9 base-uri 'self'; 10 form-action 'self';Setting frame-ancestors 'none' blocks clickjacking by preventing other sites from embedding your app inside <iframe> tags.
Quick reference
- default-src serves as the fallback policy for any un-specified resource fetch directive.
- script-src controls JavaScript execution permissions for external files and inline scripts.
- connect-src restricts target URLs for fetch(), XMLHttpRequest, EventSource, and WebSockets.
- frame-ancestors 'none' blocks clickjacking by disallowing iframe embedding.
- base-uri 'self' prevents malicious modification of relative URL base tags.
Remember this
Define explicit script-src, connect-src, and frame-ancestors directives to restrict application resource access.
Cryptographic Nonces vs Hashes with 'strict-dynamic' in Next.js
Allowlisting explicit domain names (script-src https://example.com) in CSP is brittle and prone to bypasses via JSONP endpoints. Modern CSP relies on Cryptographic Nonces and 'strict-dynamic':
1. Cryptographic Nonce: Next.js App Router Server Components generate a unique, unpredictable 128-bit random base64 string (nonce) per HTTP request:
1// middleware.ts (Next.js)2const nonce = Buffer.from(crypto.randomUUID()).toString('base64');3const cspHeader = `script-src 'self' 'nonce-${nonce}' 'strict-dynamic';`;2. 'strict-dynamic': Instructs the browser that any script explicitly trusted via a valid matching nonce="rAnd0m..." attribute is permitted to dynamically load child scripts, streamlining modern bundler code splitting.
Quick reference
- Cryptographic nonces generate a unique random base64 string on every HTTP request.
- Scripts without a matching nonce attribute are blocked instantly by the browser engine.
- 'strict-dynamic' allows trusted nonced scripts to dynamically load child JavaScript bundles.
- Eliminates the need to maintain long, fragile lists of third-party domain hostnames in CSP headers.
- Native support in Next.js App Router middleware via headers() and nonce propagation.
Remember this
Use per-request cryptographic nonces with 'strict-dynamic' to secure Next.js dynamic script bundles.
Testing with Content-Security-Policy-Report-Only & Report-To Endpoints
Deploying a strict CSP on a live production web application without prior testing can inadvertently break third-party analytics, chat widgets, or payment gateways.
Use Report-Only Mode during rollout:
1Content-Security-Policy-Report-Only: 2 default-src 'self'; 3 script-src 'self' 'nonce-rAnd0m' 'strict-dynamic'; 4 report-uri /api/security/csp-report; 5 report-to default;In Report-Only mode, the browser logs violations and POSTs JSON telemetry reports to /api/security/csp-report without blocking resource execution. Once violation telemetry shows zero false positives, transition the header to Content-Security-Policy to enforce strict blocking.
Quick reference
- Content-Security-Policy-Report-Only logs policy violations without blocking page resources.
- report-uri and Report-To endpoints receive automated JSON payloads detailing violation events.
- JSON reports include blocked-uri, violated-directive, document-uri, and line number details.
- Enables DevOps teams to eliminate false-positive blocks before enforcing strict CSP rules.
- Continuous CSP telemetry alerts security teams to active XSS attack attempts in production.
Remember this
Deploy Content-Security-Policy-Report-Only first to collect violation telemetry before enforcing strict blocking.
Key takeaway
To test CSP header configurations, inspect your browser's Developer Tools Console. Add an inline <script>console.log('test')</script> tag and verify that the browser logs a CSP violation error.
Related Articles
Explore this topic