Skip to main content

Content Security Policy (CSP): protection from XSS

Security · 19.04.2026 · 5 min read
Illustration for “Content Security Policy (CSP): protection from XSS”

Content Security Policy (CSP) is an HTTP header that tells the browser where it's allowed to load scripts, styles, images, and other resources from. It's an effective defense against XSS attacks and data injection.

Basic CSP directives

DirectiveControls
default-srcDefault source for all resources
script-srcJavaScript
style-srcCSS
img-srcImages
connect-srcAJAX, WebSocket, Fetch
font-srcFonts
frame-srciframes
report-uriURL for violation reports

CSP policy examples

Strict policy (for simple sites)

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'

For WordPress with Google Fonts and GTM

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com;
  style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' data: https://www.google-analytics.com;

Configuring via .htaccess

<IfModule mod_headers.c>
    Header always set Content-Security-Policy "        default-src 'self';         script-src 'self' 'unsafe-inline' 'unsafe-eval';         style-src 'self' 'unsafe-inline';         img-src 'self' data: https:;         font-src 'self' data:;         connect-src 'self';"
</IfModule>

Report-Only mode for testing

# Doesn't block, only sends reports
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report.php
💡 Start with Content-Security-Policy-Report-Only — collect violations for a week, then switch to a strict CSP without breaking the site.
⚠️ 'unsafe-inline' and 'unsafe-eval' weaken protection. Use a nonce or hash for inline scripts in production.

CSP violation messages explained

The browser always prints in the console which directive fired and what got blocked — there is no need to guess.

  • Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. — an inline script in the HTML was blocked; the fix is to move the code into a separate file or sign the block with a nonce or hash.
  • Refused to load the stylesheet 'https://fonts.googleapis.com/...' because it violates the following Content Security Policy directive: "style-src 'self'". — the font domain was never added to style-src, so styles from the external CDN will not load.
  • Refused to connect to 'https://api.example.com/...' because it violates the following Content Security Policy directive: "connect-src 'self'". — an AJAX or fetch request to a third-party API was blocked; the domain has to be listed explicitly in connect-src.
  • Refused to frame 'https://widget.example.com/' because it violates the following Content Security Policy directive: "frame-src 'self'". — an embedded third-party iframe will not open until its domain appears in frame-src.
  • A JSON report sent to report-uri like {"csp-report": {"document-uri": "...", "violated-directive": "script-src-elem", "blocked-uri": "...", "line-number": 42}} — this is how the browser reports a violation to the server: it shows which line and which resource triggered the block, even if the user never opened the console.

Ways to set CSP: where and when

MethodWhere it is setframe-ancestors supportWhen to use it
Content-Security-Policy HTTP headerServer response (Nginx, Apache, PHP)Yes, fullyThe main method for production
<meta http-equiv> tag in HTMLInside the page's <head>No, the directive is ignored in a meta tagWhen there is no access to server headers, for example on static hosting with no configuration
Content-Security-Policy-Report-OnlyA separate server response headerYesTest mode: collects violations but blocks nothing
Enforce and report-only togetherTwo headers at onceYesGradually tightening the active policy while collecting data on new violations

When it is too early for a strict CSP

  • The code is full of inline handlers like onclick="..." and scattered <script> tags with no attributes — a strict script-src without 'unsafe-inline' breaks them all at once; the fix is a gradual move to nonces or hashes, not flipping the switch overnight.
  • The site runs third-party widgets — a support chat, a payment form, an ad network — that inject their own inline code and pull resources from unpredictable domains; their sources have to be added to the policy by hand and kept up to date.
  • The site is served through several CDNs with subdomains that change often — a fixed list of domains in the CSP goes stale quickly and needs constant manual syncing.
  • There is no way to collect violation reports — turning on a strict policy without a Report-Only period almost guarantees breaking some functionality quietly, until users start complaining.

Verifying that the policy is actually applied

# Confirm the header is really coming from the server
curl -I https://example.com | grep -i content-security-policy

# Open DevTools → Console on the live page:
# there should be no "Refused to" lines if the policy is in enforce mode

To check the policy itself for logical mistakes (overly broad sources, a missing object-src), it helps to paste the header value into an independent online CSP analyzer, such as Google's CSP Evaluator. If report-uri is configured alongside it, reports of violation attempts keep arriving even after the policy is in enforce mode, not just during the Report-Only stage.

Was this article helpful?
← Back to Knowledge Base Ask Support