Fix librarySecurity

Security

Say which scripts your page is allowed to run

Your site does not declare which scripts it is allowed to run, so if something unwanted ever reaches a page it will run with the same authority as your own code.

Why it matters

This is the strongest protection in this library and the only one that can break your site if it is applied carelessly. A policy that is too strict blocks your own stylesheets and scripts, and the page still loads but appears unstyled and does nothing. That is why the safe path is to measure first with a report-only policy, then enforce it. A policy that names no directive governing where scripts may come from is treated as decoration by this test rather than as protection.

How you would notice it

  • A security scan reported a missing Content-Security-Policy.
  • A payment provider or compliance form asked what your pages allow scripts to do.
  • You want to limit the damage if something unwanted ever reaches a page template.

What to do

This is the repair we applied to our own site, and it took four attempts. These are the four things that went wrong first.

Stage 1Steps 1–2 · includes the code for this stage

  1. Read this before you change anything: this is the one repair in this library that can break your site. Apply it in two stages and keep the rollback step below open in a tab.

    A content policy is enforced by the browser. A mistake does not produce an error message, it produces a site that loads with no styling and no working scripts.

  2. Stage one, measure. Publish the policy in report-only mode, which reports what would be blocked without blocking anything, so the site keeps working exactly as it does now.

    Report-only will not clear this check, by design, and it earns none of the points this repair is worth, because it blocks nothing. What it changes is how the check describes your site: a measured policy is reported as an observation rather than as a missing one. It is a measuring stage, not partial credit, so expect the recommendation to remain until stage two.

Our production dispatch

The framework inlining trap

Our own site had nine inline scripts on one page, and moving them into separate files changed nothing: the build tool inlined the compiled result straight back into the HTML, five new inline blocks in place of five old ones. Each of those blocks needs its own fingerprint in the policy, and a dependency upgrade can silently change a fingerprint and take a page's interactivity down. Setting the build's asset inlining limit to zero dropped the site from ten fingerprints to one. If you use a modern build tool, look for a setting with that name before you write a list of fingerprints.

Source: Vite 8 assetsInlineLimit build artifact measurement on noblemarketglobal.com

Code for this stage
# Cloudflare Pages or Netlify: put this in your headers file
/*
  Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; object-src 'none'

# nginx, on a server you manage:
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; object-src 'none'" always;

Stage 2Steps 3–4 · includes the code for this stage

  1. Leave report-only mode in place for a real period and read what it reports. Fix or allow each item it names before you enforce anything.

    The names that usually appear are Google Fonts, analytics and tag managers, embedded video, chat widgets, payment scripts, and any inline script inside your own template. Reports only arrive somewhere if you have a report collector set up. Without one, browse the site with the developer tools console open: each blocked item is named there as you go, which is enough for a first pass.

  2. Stage two, enforce. Remove the -Report-Only part of the name, and send the policy from wherever your site can actually send a response header: the headers file on a static host, or your server configuration if you run one. The policy text is the same either way.

    Allowing unsafe-inline for styles is a deliberate compromise. Inline style attributes are very common, and blocking them is the most frequent reason a layout breaks. Allow it for styles, never for scripts. Two limits of a fixed headers file are worth knowing. It cannot mint a fresh one-time value (a nonce) for each visitor, because the same file is sent to everyone, so a nonce-based policy needs server middleware or an edge Worker and would force pre-built pages to be rebuilt on every request. And if you later change one of your own inline scripts, its fingerprint changes with it, so the policy must be updated in the same edit or that script stops running.

Our production dispatch

An injection you did not write

Seven of our pages reported a blocked script that we never added: a challenge script our host injected at the edge, carrying a value that changes with every request. No fingerprint can match bytes that differ per request, so the policy was correct and the site was still broken. The giveaway was a global variable named window._CF$cv$params appearing in the response. The documented off switch is a caching directive rather than a security one: sending no-transform alongside your cache control tells the edge not to alter the response. If a violation names a script you do not recognise, check whether your host is rewriting your HTML.

Source: Cloudflare Challenge Platform and RFC 7234 no-transform edge probe

Our production dispatch

Analytics that runs in a worker

We run third-party analytics off the main thread so it cannot slow the page down. That design needs two permissions in the policy that surprise people: the tag manager registers a service worker, and it falls back to a hidden frame when a service worker is unavailable. Without the two directives that allow those, the policy reports violations on every page of the site including the home page, and the analytics stop without saying so. If you use a tag manager that advertises a worker mode, allow the worker and the frame at the same time you allow the script.

Source: Partytown Web Worker and GA4 network telemetry verification

Code for this stage
# Cloudflare Pages or Netlify: put this in your headers file
/*
  Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'

# nginx, on a server you manage:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'" always;

Stage 3Steps 5–6

  1. If your policy also carries frame-ancestors, know what that directive did and did not buy you. It answers the separate clickjacking finding, which is the check that credits it, and it adds nothing to this one. This recommendation is cleared by the directives that govern what may execute, which in this policy are default-src and script-src.

    Do not add a second frame protection rule for the same job. Two rules for one job are harder to reason about and can disagree. If you already send X-Frame-Options, leaving it in place is fine, and browsers that understand frame-ancestors give it precedence.

  2. If the site breaks, roll back in one step: remove the Content-Security-Policy header, save, then reload the page.

    The site returns to normal immediately. Keep the report-only line if you want to keep collecting information while you work out what was blocked.

Our production dispatch

Fonts that stay on your domain

A font loaded from another company's servers has to be named in the policy forever, and it adds a request that holds up the first paint while it is resolved. We downloaded both families, served them from our own domain, and removed the two external origins from the policy entirely, which left the font rule as a single instruction to use our own files. If you take one thing from this page, take this one: it is the cheapest way to shrink a policy and speed up the page at the same time.

Source: Self-hosted WOFF2 font extraction and network request elimination

How to check it worked

Confirm it worked

Load the site and open the developer tools console. Confirm there are no blocked resource errors, then check that the styling, the images, and any interactive element still work. Check a second address if you use both www and the bare domain, and check one page that embeds something from another site. Then request an ordinary page and read the headers the browser actually received: while the policy is still report-only the recommendation stays in your report, and it clears only once the enforcing policy is the one your pages send. A policy placed inside the page HTML instead of on the response does not count, because this check reads the response. Re-run the Siege Test and confirm the Content-Security-Policy recommendation is gone.

Want this handled for you?

Bring your report to The Council and we will scope the repairs, the rebuild, or the full stronghold.