Cross-Site Scripting (XSS) is one of the oldest and most dangerous vulnerabilities on the web. It happens when a hacker injects malicious JavaScript into your site (e.g., via a comment form or a compromised plugin) to steal visitor data or redirect traffic.
While firewalls (WAFs) try to block these attacks at the door, Content Security Policy (CSP) is the bodyguard inside the building.
CSP is an HTTP header that allows you to tell the browser: “Only load scripts from MY domain and Google Analytics. Block everything else.”
If a hacker manages to inject a malicious script like <script src="hacker.com/steal.js">, the browser will check your CSP, see that hacker.com is not on the allowed list, and refuse to execute it.
In this guide, we will demystify CSP and show you how to build a basic policy for your WordPress site.
How CSP Works (The “Allowlist” Concept)
By default, browsers trust any code sent from the server. CSP changes this by introducing an Allowlist.
You define specific “Directives” for different types of content.
Common Directives
default-src: The fallback rule for everything not specified below.script-src: Controls where JavaScript can load from. (Most critical for XSS).style-src: Controls where CSS stylesheets can load from.img-src: Controls where images can load from.connect-src: Controls where the browser can send data (AJAX/API calls).
A Simple Example
HTTP
Content-Security-Policy: default-src 'self'; img-src *; script-src 'self' https://www.google-analytics.com;
Translation:
default-src 'self': Only load files from my own domain.img-src *: Load images from anywhere (e.g., Pinterest, CDNs).script-src ...: Only load JavaScript from my domain OR Google Analytics.
If a script tries to load from evil-site.com, the browser blocks it immediately.
The Challenge with WordPress
WordPress is infamous for being “CSP hostile” out of the box.
- Inline Scripts: Many themes and plugins write JavaScript directly into the HTML (
<script>doSomething();</script>) rather than loading a separate file. - Inline Styles: Customizer settings often print CSS directly into the page head (
<style>.bg { color: red; }</style>). - External Resources: You likely use Google Fonts, YouTube embeds, Facebook pixels, and Stripe scripts.
If you set a strict CSP like default-src 'self', your site will likely break immediately because all these external and inline resources will be blocked.
Step 1: Building Your Policy
For a typical WordPress site, a functional starting policy looks something like this:
HTTP
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com https://js.stripe.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' data: https://fonts.gstatic.com;
Key Terms Explained:
'self': Refers to your current domain.'unsafe-inline': Allows scripts/styles written directly in the HTML. (Ideally, we want to remove this, but for WordPress beginners, it’s often necessary to keep the site working).'unsafe-eval': Allows JavaScript functions likeeval(). Many complex page builders (Elementor/Divi) require this.data:: Allows base64 encoded images (common in logos).
Step 2: Testing with “Report-Only” Mode
Do not go live immediately. You will break something.
CSP has a magical feature called Content-Security-Policy-Report-Only.
When you use this header, the browser will not block anything. Instead, it will simply report errors to the browser console (and optionally a logging server) telling you what it would have blocked.
- Set the header to
Report-Only. - Browse your site (Home, Checkout, Contact Page).
- Open Chrome DevTools (F12) -> Console.
- Look for red warnings: “Refused to load script from…”
- Add those missing domains to your policy.
Step 3: Implementing CSP in WordPress
Once you have refined your policy, you can implement it via your server configuration.
Method A: Apache (.htaccess)
Add this to your .htaccess file:
Apache
<IfModule mod_headers.c>
Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:;"
</IfModule>
Method B: Nginx
Add this to your server block:
Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:;";
Method C: Use a Plugin (Easiest)
If you aren’t comfortable editing server files, use a plugin like Headers Security Advanced & HSTS WP. It provides a GUI to build your policy and supports “Report Only” mode easily.
Step 4: Verify with FunSentry
Setting up CSP is complex, and syntax errors (like missing a semicolon) can invalidate the whole policy.
FunSentry’s CSP Analyzer checks your live site for:
- Syntax Errors: Is your policy valid?
- Security Strength: Are you using
'unsafe-inline'? (We flag this as a warning, but understand it’s often needed). - Missing Directives: Did you forget to define
object-srcorbase-uri? (Common ways hackers bypass CSP).
Summary Checklist
| Action | Why? |
| Audit Resources | List every external service you use (Google, FB, Stripe). |
| Start with Report-Only | Test without breaking the site. |
| Allow ‘self’ | Ensure your own files can load. |
| Add Whitelist | Add domains for Analytics, Fonts, and CDNs. |
| Monitor Console | Fix errors as they appear. |
| Switch to Enforce | Change header from Report-Only to Content-Security-Policy. |
Does your site have an XSS shield?
CSP is the ultimate layer of defense against script injection. Run a free scan at FunSentry to check if your headers are correctly configured and protecting your users.
