Anticode Open builder

Content Security Policy explained for site owners

A Content Security Policy is a response header that tells the browser which sources a page may load scripts, styles, images, fonts and frames from, and where its forms may post. Anything not allowed is blocked. A strict policy turns an injected script tag from a disaster into a console error.

Updated · By the Anticode team

Where to find your site's policy

Open the site, then your browser's DevTools. In the Network panel, click the first request (the page itself) and look under Response Headers for Content-Security-Policy. Some sites set it instead in a <meta http-equiv="Content-Security-Policy"> tag in the page's head. If you find neither, the page has no policy and the browser allows everything.

A strict policy, read line by line

Here's a policy suitable for a page with no JavaScript at all, split at each semicolon:

default-src 'none'
The fallback for fetch directives not listed. 'none' means: if a type isn't explicitly allowed below, nothing of that type loads.
script-src 'none'
No scripts from anywhere, including inline ones. This is the line that matters most: injected <script> tags and onclick attributes simply don't run.
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com
Stylesheets from the site itself, inline styles, and Google's font stylesheet. Inline styles are a much smaller risk than inline scripts.
font-src https://fonts.gstatic.com
Font files only from Google's font servers.
img-src 'self' data: https:
Images from the site, data URIs, and any HTTPS address. Tighten to named hosts if you know them.
connect-src 'none'
No background requests (fetch, XHR, WebSockets). With scripts off, there's nothing to make them anyway.
form-action 'self'
Forms may only submit to this site. Important: form-action does not fall back to default-src, so it must be set explicitly.
base-uri 'none'
Stops an injected <base> tag from redirecting every relative link. Also has no fallback.
object-src 'none'
No plugins via <object> or <embed>.
frame-ancestors 'none'
Other sites can't put this page in a frame, which prevents clickjacking. This one works only as a header, not in a meta tag.

The precise rules for each directive are in the W3C's CSP Level 3 specification, and MDN's Content-Security-Policy reference is the friendlier guide.

What a strict policy blocks, and why that matters

Most website attacks that reach visitors work by getting code into the page: a comment field that accepts HTML, a compromised third-party script, a form edited to post somewhere else. A strict policy means that even if such content gets into the page, the browser refuses to execute it or send data to the wrong place. It's defence in depth: it doesn't fix the hole, but it stops the hole being used.

It also blocks things you might want, which is why policies get weakened. A chat widget, an analytics script or an embedded video each need their own allowances. Every host you add to script-src is one more party whose compromise becomes yours.

Warning signs in a policy

  • 'unsafe-inline' in script-src: inline scripts run, which defeats much of the protection. Nonces or hashes are the safer way to allow specific inline scripts.
  • 'unsafe-eval': allows strings to be run as code.
  • A bare * or https: in script-src: scripts from anywhere on the web.
  • No form-action, base-uri or frame-ancestors: these don't inherit from default-src, so leaving them out leaves them open.
  • A policy only in Content-Security-Policy-Report-Only: that header reports violations but blocks nothing.

Changing a policy without breaking the site

  1. Write the policy you want.
  2. Send it as Content-Security-Policy-Report-Only first, so violations show in the console without blocking anything.
  3. Browse every page and use every form. Note each violation and decide whether to allow that source or remove the thing loading it.
  4. Switch to the enforcing header once the console is clean.
  5. Retest after every new embed or third-party tool.

How Anticode sets it

Published pages from Anticode's design engine contain no scripts at all and ship with script-src 'none' in their policy, so there's no inline script to allow and no third-party script host to trust. Sentinel checks before publishing that a policy is set, that scripts are blocked by it, that there are no inline event handlers, and that form targets are locked to the site; the server re-checks scripts, handlers, javascript: URLs and form targets when you publish. The security checklist explains those checks in turn.

Limits

A policy with no scripts allowed means no script-based widgets on design-engine pages: no embedded chat, analytics tags or third-party booking scripts. That's the trade-off for the protection. Anticode apps, which do use JavaScript, run in their own sandbox with no network instead. And a CSP protects visitors' browsers; it does nothing for server-side problems or weak passwords. For a broader check, see how to check a website for security issues.

Questions people ask

Is a meta tag as good as a header?

Mostly, but not entirely. A meta tag can't set frame-ancestors or report-only mode, and it only applies from the point it appears in the page. A header is stronger.

Why does my policy block Google Fonts?

Fonts need two allowances: the stylesheet host in style-src and the font file host in font-src.

Does a CSP slow the site down?

No. The browser checks each request against the policy, which costs nothing noticeable.

Sources

  1. W3C: Content Security Policy Level 3, checked
  2. MDN: Content-Security-Policy, checked