Skip to main content
Back to blog
February 27, 2026|8 min read|Tutorials

Content Security Policy (CSP): A Practical Guide to Stop XSS Attacks

Learn how to configure Content Security Policy headers step by step. Prevent XSS attacks, data injection, and clickjacking with real-world examples.

ZeriFlow Team

761 words

What Is Content Security Policy?

Content Security Policy (CSP) is an HTTP security header that tells browsers which sources of content are allowed to load on your page. It is the single most effective defense against Cross-Site Scripting (XSS) attacks.

Without CSP, a browser will execute any JavaScript it encounters on your page, whether you put it there or an attacker injected it. With CSP, the browser blocks anything not explicitly allowed by your policy.

Why CSP Matters

XSS is the most common web vulnerability. It lets attackers: - Steal session cookies and hijack user accounts - Redirect users to phishing pages - Inject cryptominers that use your visitors' CPUs - Deface your website with malicious content - Exfiltrate data from forms and pages

CSP does not just detect XSS — it prevents it at the browser level.

CSP Basics

A CSP header looks like this:


Content-Security-Policy: directive1 source1 source2; directive2 source3;

Key Directives

DirectiveControlsExample
default-srcFallback for all resource types'self'
script-srcJavaScript files'self' cdn.example.com
style-srcCSS stylesheets'self' 'unsafe-inline'
img-srcImages'self' data: *.cloudinary.com
font-srcWeb fonts'self' fonts.gstatic.com
connect-srcAJAX, WebSocket, fetch'self' api.example.com
frame-srcIframes'none'
object-srcFlash, Java applets'none'
base-uriBase URL for relative URLs'self'
form-actionForm submission targets'self'

Source Values

ValueMeaning
'self'Same origin only
'none'Block everything
'unsafe-inline'Allow inline scripts/styles (weakens CSP)
'unsafe-eval'Allow eval() (weakens CSP)
'nonce-abc123'Allow specific inline scripts with matching nonce
*.example.comAllow from subdomains
https:Allow any HTTPS source
data:Allow data: URIs

Step-by-Step Implementation

Step 1: Start with Report-Only Mode

Do not jump straight to enforcing CSP. Start by monitoring what would be blocked:


Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

This logs violations without blocking anything. Check your server logs or a CSP reporting service to see what would break.

Step 2: Build Your Base Policy

Start strict and add exceptions:


Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data:;
  font-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

Step 3: Add Your CDNs and Services

If you use Google Fonts, Analytics, Stripe, etc., add them:


Content-Security-Policy:
  default-src 'self';
  script-src 'self' js.stripe.com www.googletagmanager.com;
  style-src 'self' 'unsafe-inline' fonts.googleapis.com;
  img-src 'self' data: *.google-analytics.com;
  font-src 'self' fonts.gstatic.com;
  connect-src 'self' api.stripe.com *.google-analytics.com;
  frame-src js.stripe.com;
  object-src 'none';
  base-uri 'self';

Step 4: Handle Inline Scripts with Nonces

Instead of using 'unsafe-inline' (which weakens CSP), use nonces:

html

<!-- Server generates a unique nonce per request -->
<script nonce="a1b2c3d4e5">
  console.log('This is allowed');
</script>

Content-Security-Policy: script-src 'self' 'nonce-a1b2c3d4e5';

The nonce must be cryptographically random and different on every page load.

Step 5: Switch to Enforcement

Once you have confirmed no legitimate resources are blocked, remove Report-Only:


Content-Security-Policy: default-src 'self'; ...

Server Configuration Examples

Nginx

nginx

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';" always;

Apache

apache

Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';"

Next.js (next.config.js)

javascript

const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none';"
  }
];

Common Mistakes

  1. 1Using `'unsafe-inline'` and `'unsafe-eval'` everywhere — This basically disables CSP protection. Use nonces instead.
  2. 2Setting `default-src *` — This allows everything and defeats the purpose.
  3. 3Forgetting `object-src 'none'` — Flash and Java applets are attack vectors.
  4. 4Not testing in report-only mode first — You will break your site.
  5. 5Setting CSP once and forgetting — New third-party scripts need to be added to the policy.

Check Your CSP

Run a ZeriFlow scan to instantly check if your website has a Content Security Policy header and how well it is configured. The scan evaluates your CSP along with 80+ other security checks.

Conclusion

Content Security Policy is the most powerful browser-side defense against XSS. Start with report-only mode, build your policy gradually, use nonces instead of unsafe-inline, and test regularly. Your users' security depends on it.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading