A content security policy for WordPress that does not break the editor

A content security policy tells the browser which sources it is allowed to execute. Done properly, it turns most cross-site scripting from a breach into a blocked console message. Most WordPress sites do not run one, and the reason is usually the same: somebody tried, the block editor stopped working, and the policy was quietly abandoned.

Apply it to the front end only

This is the whole trick. The public front end is where untrusted visitors meet your markup, and that is where a strict policy belongs. The administration area is authenticated, and the block editor legitimately needs inline and evaluated script in order to function at all.

So gate the header: skip it for administration screens, the login page, the REST API and cron. What remains is the surface that actually faces the internet.

Be strict where it counts

The directive that stops cross-site scripting is the script source. Set it to your own origin plus a nonce generated once per request, with no allowance for inline script and none for evaluated code. Then attach that nonce to the script tags your theme emits — WordPress provides filters for exactly this, so you are not rewriting output by hand.

The honest complication is the style source. WordPress prints the theme configuration stylesheet and block layout rules as inline style tags, and core offers no filter for adding a nonce to them. You have two options: permit inline styles and accept that the residual risk there is style injection rather than script execution, or buffer the response and add the nonce to those tags yourself. The first is the right default for most sites. The second is worth having available for the client whose security review asks for it.

Lock down the rest

  • Object sources set to none — there is no legitimate use for plugins in a modern site.
  • Base URI restricted to your own origin, which stops an injected base tag quietly redirecting every relative URL on the page.
  • Form action restricted to your own origin, which stops an injected form posting a visitor’s input somewhere else.
  • Frame ancestors set deliberately: this, rather than the older header, is what actually prevents clickjacking today.

The cheapest win is fewer third parties

Every external script you add is an origin you must permit and a company whose compromise becomes your compromise. Self-host your fonts. Question each tag manager entry. A policy is easy to write when there is very little to allow, and the site is faster for precisely the same reason.

Test before you enforce

Ship the policy in report-only mode first, watch what it would have blocked, and fix those things before enforcing it. Then set it and leave it — and the next time you add an embed, add its origin rather than an exception.

Something that has to work?

Send us the problem. We will tell you what it takes to solve it, what it costs and when it can be live.