How to find out why your CSP is blocking a script

A debugging runbook for "Refused to load the script because it violates the following Content Security Policy": read the effective directive, identify the usual suspects, fix with a hash or nonce, and confirm at scale with violation reports.

Published · Updated

The error looks like this:

Refused to load the script 'https://cdn.example.com/widget.js' because it
violates the following Content Security Policy directive: "script-src 'self'".

The fix in one paragraph: the message names the effective directive (here script-src) and the blocked resource. Find where that policy is set, decide whether the script is legitimate, and if it is, authorize it in that directive, preferably with a hash or nonce rather than a broad origin. If there is no script-src in your policy, the browser falls back to default-src, so that is the line to edit. Everything below is the runbook for doing this without guessing.

Step 1: read the console message properly

Browsers pack everything you need into that one line, and most people skim past it.

The effective directive tells you which allowlist failed. script-src-elem means a script tag, script-src an execution rule, style-src a stylesheet someone mislabeled as a script problem. If the message says the policy directive is default-src, your policy has no script-src at all and scripts are governed by the fallback. That surprises people who added script-src to one header and not the other.

The blocked URL tells you what tried to load. https://cdn.example.com/widget.js is an external fetch. An empty URL or the word inline means an inline script block. eval means a string-to-code call. chrome-extension://... means it was never your code.

For inline scripts, Chrome goes further and prints the SHA-256 hash of the blocked block. That hash is a ready-made fix: paste it into the directive and that exact script is authorized. Keep that thought for step 2.

One more check before touching anything: is the policy enforced or report-only? A Content-Security-Policy-Report-Only header logs the same violations but blocks nothing, and some directives behave differently there (upgrade-insecure-requests is ignored entirely). If users report breakage but your header is report-only, the CSP is not the culprit.

Step 2: the usual suspects

After enough 2 am pages, the same five causes keep turning up.

A legitimate new third-party script. Marketing added a tag, a vendor moved to a new CDN, an npm dependency started pulling from a different origin. The lazy fix is adding the origin to script-src. The better fix is a hash or nonce, because an origin allowlists everything that host will ever serve, including the compromised version of the script. The trade-offs between the two are their own topic: nonces vs hashes.

An inline script without a nonce or hash. The moment your policy has script-src without 'unsafe-inline', every inline block needs explicit authorization. Here is the before and after for a small inline snippet:

# Before: the inline script is blocked
Content-Security-Policy: script-src 'self'
# After: hash printed by the console error, pasted into the directive
Content-Security-Policy: script-src 'self' 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8='
<script>document.getElementById('year').textContent = '2026';</script>

The hash covers the script byte for byte. Reformat the snippet, change a character, and the digest no longer matches, so this suits stable snippets. For templated inline scripts, use a nonce instead.

eval or a string-to-code call. The error mentions 'unsafe-eval'. Some library is calling eval(), new Function(), or setTimeout with a string. Adding 'unsafe-eval' makes the error go away and reopens the exact class of injection CSP exists to close. Our position: this is usually a library to replace, not a directive to add. Old template engines and runtime expression parsers are the repeat offenders, and most have precompiled or CSP-safe builds.

A browser extension. Password managers, ad blockers, coupon extensions all inject scripts, and your policy blocks them. The console shows chrome-extension:// sources or inline hashes that match nothing you shipped. Not your bug. Do not loosen the policy for them. Filter them out of your reports and move on.

Two policies fighting. A CSP <meta> tag left over from a prototype plus a header from your CDN means both apply, and a script must satisfy every policy present. You can widen the header all day and the meta tag keeps blocking. Grep your templates for http-equiv="Content-Security-Policy" whenever a fix mysteriously does nothing.

Step 3: one console is one browser

DevTools shows you the violation you can reproduce, in your browser, on your machine. The user on Safari 16 with an enterprise proxy sees a different set. Once the question becomes “which pages are affected, which browsers, since when”, the console stops being the right tool and your report-to stream becomes the source of truth.

This is the part we built CentralCSP reporting for: every violation indexed by directive, origin, hash and document URL, so that question is a filter rather than an archaeology session. Filter on the blocked origin and you get the affected pages and the first-seen date in seconds. Setup is one header pointing at the managed endpoint, about 2 minutes, with 90-day retention on every plan, the €39.99/mo Start plan included.

And for the page you are debugging right now, the free Chrome extension has an Observe mode that shows the live violation stream as you click through, no account, entirely local. Its Rewrite mode then lets you test a candidate policy against the live site before you deploy the fix, which beats the deploy-refresh-squint loop.

The runbook, compressed: read the directive and the URL, match against the five suspects, fix with the narrowest authorization that works, then check the report stream to confirm the fix landed for everyone and not just for you.

Frequently asked questions

What does "Refused to load the script because it violates the following Content Security Policy" mean?

The browser fetched your page, read its Content-Security-Policy, and found that a script (external or inline) is not authorized by the relevant directive, usually script-src. The console message names the blocked URL and the effective directive. Fix it by adding the script origin, or better a hash or nonce, to that directive.

Why is my inline script blocked by CSP?

A policy with script-src blocks all inline scripts unless the policy contains unsafe-inline (bad), a matching nonce, or a matching hash. The console error for inline scripts includes the SHA-256 hash the browser computed. Adding that exact value to script-src authorizes that exact script and nothing else.

Do browser extensions cause CSP violations?

Constantly. Extensions inject scripts and styles into pages, and the page policy blocks them like any other unauthorized code. In reports they show up as chrome-extension:, moz-extension:, or odd inline hashes that no deploy explains. They are not your bug: filter them out rather than loosening the policy.

Why is my script still blocked after I updated the CSP header?

Check for a second policy. A CSP in a meta tag and one in the header both apply, and a resource must pass every policy present, so the strictest one wins. Also check caches and CDNs serving the old header, and confirm the directive you edited is the effective one: with no script-src, default-src governs scripts.