Skip to main content
Security
Published 11 min read

Content-Security-Policy (CSP) Explained With Examples

CSP is an allow-list for where your page's resources can load from. Here's how the directives fit together.

CSP is fundamentally an allow-list: you declare which sources are trusted for each resource type, and the browser blocks anything that doesn't match. This is a walkthrough of the directives that come up most in practice, with realistic examples.

The basic shape

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; img-src 'self' data:

Each directive is a resource type followed by a space-separated list of allowed sources, and directives are separated by semicolons.

Common directives

DirectiveControls
default-srcFallback for any resource type without its own directive
script-srcWhere JavaScript can load from
style-srcWhere CSS can load from
img-srcWhere images can load from
font-srcWhere fonts can load from
connect-srcWhere fetch/XHR/WebSocket connections can go
object-srcControls <object>/<embed>/<applet> - usually set to 'none'
frame-srcWhich origins can be loaded in an iframe on this page
frame-ancestorsWhich origins are allowed to frame this page (clickjacking protection)
base-uriRestricts what a <base> tag can set as the document's base URL
form-actionRestricts where forms on this page can submit to

Source values

ValueMeaning
'self'The page's own origin
'none'Nothing is allowed
'unsafe-inline'Allows inline scripts/styles - weakens the policy significantly
'unsafe-eval'Allows eval() and similar string-to-code APIs
https://example.comA specific trusted origin
*Any origin - effectively disables the restriction for that directive

A realistic, reasonably strict policy

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

This allows the page to load its own scripts, styles, and images (plus inline data: URIs for images, common for small icons), blocks plugin content entirely, prevents base-tag hijacking, restricts form submissions to the same origin, and only allows same-origin framing.

Common weaknesses

  • 'unsafe-inline' in script-src - permits inline scripts and event handlers, which removes most of CSP's XSS mitigation value. The fix is migrating to nonces (script-src 'nonce-<random>') or hashes for any inline scripts that are genuinely needed.
  • 'unsafe-eval' - allows eval() and similar APIs, a common vector for executing injected code.
  • A wildcard (*) or bare scheme (https:) in script-src - allows scripts from literally any host.
  • No object-src 'none' - leaves plugin content (a legacy code-execution vector) unrestricted by default.
  • Missing frame-ancestors - leaves clickjacking protection entirely to X-Frame-Options, if it's set at all.
// Weak - defeats most of CSP's purpose
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' *

Report-only mode

Content-Security-Policy-Report-Only uses the exact same directive syntax but only monitors and reports violations without blocking anything - useful for testing a new or tightened policy against real traffic before switching to enforcement.

Content-Security-Policy-Report-Only: default-src 'self'

Checking your policy

Paste your CSP header into the HTTP Security Headers Analyzer to check for missing directives, unsafe-inline/unsafe-eval usage, wildcard sources, and permissive frame-ancestors - along with the rest of your security header configuration in one pass. For a directive-by-directive review of the policy on its own, use the CSP Analyzer.

Common mistakes

  • Adding 'unsafe-inline' as a quick fix for a broken inline script, instead of migrating to nonces or hashes.
  • Forgetting that CSP applies per-page, not site-wide by default - each response needs the header unless it's set centrally at a proxy/CDN layer.
  • Not testing a new policy in report-only mode first, and breaking functionality in production.
  • Setting default-src to a wildcard "to be safe" and functionally disabling the policy.

Summary

CSP directives each govern one resource type's allow-list. The value comes from being specific - listing actual trusted origins rather than wildcards, avoiding 'unsafe-inline'/'unsafe-eval', and restricting object-src and frame-ancestors even if you don't tighten anything else.

Frequently asked questions

What does Content-Security-Policy do?
It lets a server declare an allow-list for where scripts, styles, images, and other resource types are permitted to load from - the browser blocks anything that violates the policy.
What does 'unsafe-inline' do in a CSP?
It permits inline <script> blocks and inline event handlers to execute, which removes most of CSP's protection against injected scripts - it's one of the most common CSP weaknesses.
What's the difference between Content-Security-Policy and Content-Security-Policy-Report-Only?
The enforcing header blocks violations; the report-only version only logs/reports them without blocking anything, which is useful for testing a policy before turning on enforcement.

The tool that pairs with this guide.

Did this help you tighten your security headers?

If these free developer tools save you time, consider buying me a byte. Your support helps keep DevCraftKit free and growing.

Buy me a byte 💾