If an attacker manages to inject a single <script> tag into your page — through a vulnerable dependency, a comment field, or a compromised third-party widget — they can read session cookies, exfiltrate form data, or swap your payment form for theirs. A Content Security Policy (CSP) is the browser-level rulebook that decides whether that injected script is allowed to run at all.
It's one of the highest-impact security controls you can add to a website, and one of the most commonly missing. Here's what it actually does, why it matters, and how to roll one out without bricking your site.
What a Content Security Policy Actually Does
A CSP is an HTTP response header (or a <meta> tag) that tells the browser which sources of content are trusted. When the browser loads your page, it checks every script, style, image, font, iframe, and fetch request against that policy. Anything not explicitly allowed gets blocked — and optionally reported back to an endpoint you control.
A minimal example sent as a header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
That single header tells the browser:
- Only load resources from your own origin by default
- Only execute scripts from your origin or
cdn.example.com - Block all
<object>,<embed>, and Flash content - Prevent attackers from changing the
<base>tag to hijack relative URLs - Refuse to be framed by any external site (clickjacking protection)
Why It Matters: The Threats CSP Stops
Cross-Site Scripting (XSS)
XSS remains in the OWASP Top 10 year after year. Even with input sanitisation, modern apps have huge surface areas — Markdown renderers, rich text editors, URL parameters reflected in error pages, third-party scripts that get compromised. A strict CSP means that even if attacker-controlled HTML makes it onto the page, the injected script won't execute because it doesn't match an allowed source or nonce.
Supply chain attacks
When Magecart-style attackers compromise an analytics or chat widget, they typically inject code that exfiltrates form data to an attacker-controlled domain. A CSP with a tight connect-src directive blocks the outbound request, neutralising the attack even though the script itself loaded.
Clickjacking
The frame-ancestors directive replaces the older X-Frame-Options header and stops other sites from embedding yours in an iframe to trick users into clicking hidden controls.
Mixed content and protocol downgrade
upgrade-insecure-requests forces every HTTP subresource to load over HTTPS, which is critical when you have legacy templates or user-generated content with http:// URLs.
The Directives That Actually Matter
CSP has dozens of directives, but in practice you'll care about a handful:
- default-src — the fallback for everything you don't explicitly set
- script-src — the single most important directive; controls JavaScript execution
- style-src — controls CSS, including inline styles
- img-src — usually permissive (
data:,https:) but worth scoping - connect-src — controls
fetch(),XMLHttpRequest, WebSocket destinations - frame-src and frame-ancestors — who you can embed, and who can embed you
- font-src, media-src, object-src — usually set to
'self'or'none' - base-uri — almost always
'self'or'none' - form-action — restricts where forms can submit, blocks credential-phishing redirects
Deploying CSP Without Breaking Production
The fastest way to ship a broken site is to apply a strict CSP cold to a legacy app. Use this rollout instead:
- Start in report-only mode. Send
Content-Security-Policy-Report-Onlywith a strict policy and areport-toendpoint. The browser will log violations but enforce nothing. - Collect violation reports for at least a week. You'll discover every inline script, tracking pixel, and third-party iframe your site actually uses — including ones marketing added without telling you.
- Refactor inline scripts. Move them to external files, or add a per-request
nonce. Avoid'unsafe-inline'onscript-srcat all costs — it effectively disables XSS protection. - Switch to enforcement. Change the header name to
Content-Security-Policyand keep monitoring reports. - Tighten over time. Remove wildcards, drop unused sources, and lock down
connect-src.
Nonces vs Hashes vs Allowlists
Allowlists (script-src https://cdn.example.com) are easy but brittle — and research has shown that around 95% of CSP allowlists are bypassable due to JSONP endpoints or hosted user content on the allowed CDNs.
Modern strict CSPs use a per-request nonce:
Content-Security-Policy: script-src 'nonce-r4nd0m123' 'strict-dynamic'; object-src 'none'; base-uri 'none'
Every <script> tag your server renders includes nonce="r4nd0m123". The 'strict-dynamic' keyword lets trusted scripts load additional scripts they need, without you maintaining a CDN allowlist. This is the recommended approach for any new build.
Common Mistakes That Make CSP Worthless
- Using
'unsafe-inline'inscript-src— this allows every inline script, including injected ones. It defeats the entire purpose. - Using
*as a source — even forimg-src, this is broader than you need. - Forgetting
object-src 'none'— Flash and plugin content can bypassscript-srcrestrictions. - Forgetting
base-uri— without it, an injected<base>tag can hijack every relative script URL on the page. - Setting CSP only via meta tag —
frame-ancestors,report-uri, andsandboxare ignored when delivered this way. Use the HTTP header. - Not testing third-party tools — Stripe, Intercom, Google Tag Manager, and most analytics tools each need specific sources added. Check each vendor's CSP documentation.
Verifying Your Policy
Once your CSP is live, you need to confirm it's actually being delivered correctly and that it's strict enough to matter. A header that reads default-src *; script-src * 'unsafe-inline' is technically a CSP but provides essentially no protection.
Tools like WebSentry parse your CSP and grade it against current best practice — flagging 'unsafe-inline', missing object-src, wildcard sources, and missing companion headers like X-Content-Type-Options and Strict-Transport-Security. Running a scan after each deploy catches regressions, like when a developer adds 'unsafe-eval' to make a new library work and forgets to remove it.
If you want to see how your site's CSP stacks up — along with SSL, headers, cookies, DNS, and CORS — run a free scan at websentry.dev and you'll get a graded report in under a minute.
Check your own site
Run a free security scan and see if your site has the issues covered in this article. Results in under 30 seconds.