What CSP actually does
By default, the browser runs whatever JavaScript a page tells it to run. If an attacker slips a <script> tag into a comment field, a URL parameter, or a third-party library you depend on, the browser executes that code with the same privileges as your own.
Content Security Policy (CSP) is an HTTP header that tells the browser: “only run scripts from these sources, only send data to these endpoints, only load images from these places.” Anything outside that list gets blocked, no questions asked.
Think of it as an allowlist for everything the page is allowed to do. You ship it once in a header, the browser enforces it on every request.
The third-party problem
Every analytics SDK, payment widget, or chat tool you embed runs with full access to your DOM. That includes the password field, the credit card form, and the cookies. If any of those vendors gets compromised (or just decides to misbehave), they can read all of it.
CSP narrows what those scripts are allowed to do, even after they load.
Why this matters legally
CSP is not optional polish. Article 32 of the GDPR requires “appropriate technical and organizational measures” for any system handling personal data. If you handle emails, names, or card numbers and an XSS attack drains them, regulators will ask why a basic CSP header was not in place.
A Magecart-style attack (where malicious JS skims card data at checkout) is hard to pull off against a site with a tight connect-src and script-src. Fines under GDPR can reach 4% of global turnover, but the practical point is simpler: a header you can ship in an afternoon prevents one of the most common exfiltration patterns on the web.
The directives that matter
CSP works through directives. Each one controls a category of resource. These are the ones you actually need to know:
default-src 'none': the safe starting point. Block everything, then allow specific things below. Aggressive, but it stops you from forgetting a category.script-src: where JavaScript can come from. In 2026, skip the long allowlist of trusted domains. Use a nonce or a hash instead (more on that below).connect-src: where the page is allowed to send data via fetch, XHR, or WebSockets. If a malicious script tries to POST your session cookie tohacker.com, this is the directive that blocks it.img-src: limits where images load from. Useful against tracking pixels and rendering exploits.frame-ancestors: who can embed your page in an iframe. This replaces the olderX-Frame-Optionsheader and protects against clickjacking.
That is enough to cover the common attack patterns. You can layer more directives later (style-src, font-src, media-src), but starting with these five gets you most of the protection.
From allowlists to strict-dynamic
The old way was a long list of trusted domains: https://www.google.com, https://cdn.jsdelivr.net, and so on. Two problems with that:
- Maintenance: a CDN moves a subdomain, your site breaks.
- Bypass: if any domain on the list hosts a vulnerable library (think
ajax.googleapis.comand an old AngularJS version), an attacker can load that library and walk around your CSP.
CSP Level 3 fixed this with 'strict-dynamic'. The idea: you authorize one script with a nonce (a random value the server generates per request and includes both in the header and in the script tag). Any script that authorized script loads is also trusted. Any script the browser sees without a valid nonce is blocked.
This means you stop maintaining a domain list. You sign one entry script, and it bootstraps whatever third-party tools you need. Smaller config, fewer broken deploys, harder to bypass.
A real case: British Airways, 2018
A Magecart group injected 22 lines of JavaScript into a script BA used on their checkout pages. Those 22 lines captured payment form data and POSTed it to an attacker-controlled server.
A connect-src 'self' directive would have blocked the POST. The browser would have refused to send card data to a domain that was not on the allowlist. The attack would have failed silently.
The initial GDPR fine was £183 million. It was later reduced, but the point holds: a single header would have stopped the exfiltration step.
Malicious browser extensions
Sometimes the bad code does not come from your stack at all. A user installs a sketchy Chrome extension, and that extension injects scripts into every page they visit, including yours. A strict CSP blocks those injected scripts from running, which protects the user even when their own browser is the source of the problem.
How to roll it out without breaking production
CSP can break things if you turn it on cold. Two practical steps:
- Start in report-only mode. Send
Content-Security-Policy-Report-Onlyinstead ofContent-Security-Policy. The browser will report violations to an endpoint you specify, but it will not block anything. Run it for a week or two, see what fires, fix the legitimate cases. - Then switch to enforcing. Once the report log is clean, swap the header name. Keep a reporting endpoint live so you catch new violations as they appear.
If a vendor tells you their script needs 'unsafe-inline', push back. Most modern SDKs work fine with nonces or async loading. Hashes are an option for static inline scripts you control. For the broader header stack, pair CSP with HSTS to stop SSL stripping on the transport layer.