← All guides
Medium

Content Security Policy: a bouncer for your web pages

The single most powerful protection against malicious scripts being injected into your site — and why most small business sites do not have one.

8 minute read · Browser protections

The short version

A Content Security Policy (CSP) is a list your website gives the browser saying "only run scripts, load images and fetch data from *these* places". If an attacker manages to inject their own script into one of your pages — the most common kind of website attack — a good CSP means the browser simply refuses to run it. It is the single most effective protection against that whole class of attack, and most small business websites do not have one.

The problem it solves

The most common way websites are attacked is not by breaking into the server. It is by sneaking a small piece of malicious code onto a page so that it runs in your *visitors'* browsers. That is cross-site scripting (XSS). It happens through a vulnerable plugin, a comment form that does not clean its input, a compromised third-party widget, or an advert network that got infected.

Once the attacker's script is running in a visitor's browser, on your page, it can do anything your page can: read what they type (including card numbers in a checkout), steal their login cookie, quietly redirect them, or replace your payment form with a lookalike. The infamous Magecart attacks that skimmed card details from British Airways and Ticketmaster were exactly this.

Here is the thing: you cannot guarantee you will never have an XSS hole — plugins, themes and third-party scripts are all out of your direct control. A CSP accepts that and adds a second line of defence: even if malicious code gets onto the page, it does not get to run.

How it works, with an analogy

Think of a CSP as the guest list at the door of a private event. Your page says to the browser: "Scripts are allowed from my own domain and from Google Analytics. Images from my domain and my image CDN. Fonts from Google Fonts. Nothing else." The browser is the bouncer. When a script turns up from evil-attacker.net, it is not on the list, and it does not get in — no matter how it got onto the page.

A policy is just a header sent with each page. A reasonable starting point for a typical business site looks like:

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

Each part names a *type* of thing (scripts, styles, images, fonts) and the places it may come from. 'self' means your own domain.

The catch: unsafe-inline

Many sites, especially WordPress sites, have small bits of JavaScript written directly inside the page rather than in separate files — "inline" scripts. A strict CSP blocks those too, because an injected attacker script is *also* inline. The lazy way round it is to add 'unsafe-inline' to the script list, which allows all inline scripts — including the malicious ones. That is why we flag unsafe-inline in script-src as weakening the policy: it reopens the exact door the CSP is meant to close.

The proper fix is to move inline scripts into files, or to use nonces or hashes — a one-time code on each legitimate inline script that the policy whitelists. This is more work, and for a small site it is reasonable to start with a policy that allows inline *styles* (much lower risk) but keeps *scripts* strict.

How to add one without breaking your site

A CSP that is too strict will block things your site needs — a chat widget, an embedded map, your analytics — and parts of the page will stop working. So the safe way to roll one out is in stages:

  1. Start in report-only mode. Send the header as Content-Security-Policy-Report-Only instead. The browser logs what *would* have been blocked (in the developer console, and optionally to a reporting address) but blocks nothing. Your site keeps working exactly as before.
  2. Browse your site for a week and collect what gets reported — every legitimate third-party script, image host and font you use.
  3. Add those sources to the policy, keep the rest locked down, and test the key pages (home, contact form, checkout).
  4. Switch the header to enforcing (Content-Security-Policy) once the reports are quiet.

On Apache/LiteSpeed hosting you set the header in .htaccess with Header always set Content-Security-Policy "...". On WordPress, plugins such as "HTTP Headers" or a security plugin with a headers section can manage it. Cloudflare users can add it under Rules → Transform Rules → Modify Response Header.

A CSP is the one security header that genuinely takes some care to get right. If you are not confident, it is a reasonable thing to ask your web developer to set up — it is a couple of hours of work and it protects your customers' card details.

Common questions

My site is simple and has no logins or payments. Do I need a CSP?
It is lower priority than for an online shop, but still worthwhile: injected scripts on even a brochure site can redirect your visitors to scams, which damages your reputation and search ranking. Start with report-only mode — it costs nothing and breaks nothing.
Will a CSP stop my Google Analytics / chat widget / embedded map?
Only if you leave them out of the policy. Report-only mode shows you exactly what to allow before anything is blocked.
Why does VigilScan flag unsafe-inline but not the rest of my policy?
Because unsafe-inline in script-src specifically re-enables the attack a CSP exists to prevent. The rest of your policy may be fine; that one directive undermines it.

Not sure if this affects your site?

Run a free check — it takes about 90 seconds and tells you exactly where you stand.

Check my website