Cybersecurity 3 min read

Content Security Policy: The Header That Stops Most XSS Attacks

Cross-site scripting is still one of the most common web vulnerabilities. A well-configured Content Security Policy is the most effective defense that does not require changing your application code.

TL;DR

A Content Security Policy header tells the browser which sources are allowed to load scripts, styles, and other resources. A strict policy stops most XSS attacks even when your application has a vulnerability.

Cross-site scripting (XSS) works by injecting malicious scripts into a page. The browser runs them because it cannot tell the difference between your code and the attacker's. Content Security Policy (CSP) is a response header that changes this: you tell the browser exactly which sources are allowed to load scripts, and it refuses to run anything else.

A properly configured CSP does not prevent the injection. It prevents the execution. Even if an attacker manages to inject a <script> tag, the browser will block it.

How It Works

You send a header like this from your server:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'

The browser reads this and enforces it for every resource loaded on the page. default-src 'self' means that by default, everything must come from the same origin. script-src 'self' https://cdn.example.com means scripts can only load from the same origin or that specific CDN. Anything else gets blocked.

The Directives That Matter Most

script-src: Controls JavaScript. This is the most important directive. 'self' alone is a good start. Adding 'unsafe-inline' or 'unsafe-eval' largely defeats the purpose because most XSS payloads are inline scripts or use eval.

style-src: Controls CSS. 'unsafe-inline' here is less dangerous than in scripts, but still worth avoiding if you can.

default-src: A fallback for any directive you do not specify explicitly. Set it to 'self' or 'none' and then explicitly allow only what you need.

img-src: Controls image loading. Worth restricting if your app handles user-supplied content.

frame-ancestors: Prevents your page from being embedded in an iframe on another site, which stops clickjacking. frame-ancestors 'none' is the strictest setting.

connect-src: Controls which origins your JavaScript can make fetch or XMLHttpRequest calls to. If your app only talks to your own API, set this to 'self'.

report-uri or report-to: Tells the browser where to send violation reports when something is blocked. Start with CSP in report-only mode (Content-Security-Policy-Report-Only) to see what would break before enforcing.

The Patterns That Defeat CSP

Wildcards: script-src * or script-src https: allows scripts from any HTTPS origin. This is almost as bad as having no CSP at all. Attackers can host their payload on any HTTPS site.

unsafe-inline: Required by many older analytics and widget scripts that inject inline code. If you cannot avoid it, use nonces instead: generate a random nonce per request, include it in the header (script-src 'nonce-abc123'), and add that nonce attribute to your legitimate inline scripts. The attacker's injected script will not have the nonce and will be blocked.

unsafe-eval: Required by some templating libraries and older frameworks. Avoid it if possible. It allows JavaScript to turn strings into code, which is precisely what many XSS attacks rely on.

Allowing too many domains: Every domain you add to script-src is a potential bypass if that domain itself serves user-controlled content. Google's CDN, for example, hosts Google Apps Script which an attacker can use to host their payload if https://script.google.com is in your allowlist.

How to Deploy One Without Breaking Your Site

  1. Start with Content-Security-Policy-Report-Only and a broad policy. No enforcement, just reporting.
  2. Look at the violation reports for a week. You will see every resource that is blocked.
  3. Add the legitimate sources to your policy. Remove or replace anything that requires unsafe-inline.
  4. Switch to Content-Security-Policy enforcement once the violation reports are clean.

The CSP Analyzer and CSP Generator tools on this site both help with this. The analyzer checks an existing header for dangerous directives. The generator lets you build a policy interactively and copy the finished header.

A CSP is not a substitute for fixing XSS vulnerabilities. It is a defense-in-depth measure that limits the damage when something gets through. The two together are much stronger than either alone.

Khodor Ghalayini

Khodor Ghalayini

Engineer · AI Builder · Cybersecurity Practitioner

Engineer with 10+ years in systems and project management. I build AI-powered tools to help people work smarter — starting with the security and productivity problems I've personally run into. More about me →