Skip to content

Web Security: Content Security Policy (CSP)

CoreConceptAugust 3, 20269 min read

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.

Content Security Policy (CSP) browser security architecture with cryptographic nonces, strict-dynamic, and report-to telemetry
Content Security Policy (CSP) browser security architecture with cryptographic nonces, strict-dynamic, and report-to 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.

Browser CSP script validation lifecycle comparing authorized nonced script execution vs un-authorized XSS blocking
Browser CSP script validation lifecycle comparing authorized nonced script execution vs un-authorized XSS blocking

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.

Browser CSP script validation lifecycle comparing authorized nonced script execution vs un-authorized XSS blocking
Browser CSP script validation lifecycle comparing authorized nonced script execution vs un-authorized XSS blocking

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.

Share:

Related Articles

Cyberattacks against web applications continue to escalate in frequency and sophistication. According to security indust

Read

Traditional perimeter-based security ('Castle and Moat') assumes that all traffic inside a private network or Kubernetes

Read

Containers are the foundation of modern cloud deployment, but default container images often ship with bloated Linux OS

Read

Explore this topic

Keep learning

Follow a structured path or browse all courses to go deeper.