Refused to execute inline script because it violates the Content Security Policy
Your page works without a Content-Security-Policy, but with one the console fills with “Refused to execute inline script” and buttons stop working. The policy is doing its job; the inline code just needs a way to prove it's yours.
Read the error
Refused to execute inline script because it violates the following Content Security Policy
directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash
('sha256-tbHiLfFxk+3UKR1aZbQCNmx+rx8ndFa/zkXGLnfe+Ic='), or a nonce ('nonce-...')
is required to enable inline execution.The directive in quotes is the one that decided, even if your policy only has default-src (it is the fallback). The hash is the SHA-256 of the exact script text the browser saw, so you can copy it straight into the policy if the script never changes. (This one is the hash of startApp().)
Which fix to use
| Situation | Best fix |
|---|---|
| Your own small inline script | Move it to /js/app.js and load it with <script src>; 'self' then covers it |
| Server-rendered page (Express, Django, Rails, Next.js) | A fresh random nonce on every response, on the header and each <script> tag |
| Static page, script never changes | Add its 'sha256-…' hash |
onclick="…" or href="javascript:…" | Use addEventListener in a file; nonces never apply to attributes |
| Third-party snippet that injects more scripts (analytics, tag managers) | Nonce on the snippet plus 'strict-dynamic' |
| Message says “evaluate a string as JavaScript” | That's eval, new Function or setTimeout("string"), not an inline script: remove it, or as a last resort add 'unsafe-eval' |
Using a nonce
// Express
import crypto from 'node:crypto';
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
res.setHeader('Content-Security-Policy',
`script-src 'nonce-${res.locals.nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'`);
next();
});
// template
<script nonce="<%= nonce %>">startApp()</script>The nonce must be new for every response and unguessable. A nonce hard-coded in a config file, or a page served from a cache with the same nonce for everyone, gives no protection. The browser hides the attribute value from scripts on the page, so it can't be read back by injected code.
Using a hash
Hash exactly the text between <script> and </script>, including leading spaces and the final newline, then add it in single quotes: script-src 'self' 'sha256-…='. A minifier or a template that changes indentation changes the hash, which is the usual reason a hash "stops working". The CSP explainer computes the hash in your browser and can test the script against your whole policy.
Inline styles have the same rule
Refused to apply inline style is the style-src version. <style> blocks accept nonces and hashes like scripts. style="…" attributes only work with 'unsafe-inline' (or 'unsafe-hashes' plus a hash), so move them into classes. Many sites accept style-src 'self' 'unsafe-inline' as a trade-off; inline CSS is far less dangerous than inline script.
Frameworks
- Next.js: generate the nonce in middleware, set the header there, and Next adds it to its own scripts when it finds it in the request's CSP header. Pages using a nonce must be rendered per request, not statically.
- Vite / Create React App builds: production builds load external files, so
'self'works; the dev server and some plugins inject inline scripts, so test against the built site. - Django: the
django-csppackage addsrequest.csp_noncefor templates. - Helmet (Express): the default policy is
script-src 'self', so inline scripts that worked before Helmet stop. Add a nonce incontentSecurityPolicy.directives.
Roll it out without breaking things
Send the policy as Content-Security-Policy-Report-Only first (see CSP Report-Only vs Enforce). The browser reports every violation in the console (and to report-uri if set) but blocks nothing, so you can fix them before switching to the enforcing header.
Related
Mixed Content blocked · CORS errors explained · CSRF token missing or incorrect · CSP header explainer · Refused to display in a frame · X-Frame-Options explainer · CSP Report-Only vs Enforce · Refused to connect (connect-src) · Trusted Types
Questions
How do I fix 'Refused to execute inline script'?
Move the script into a .js file, or allow it with a per-response nonce or the sha256 hash quoted in the error. Adding 'unsafe-inline' also works but removes most of the protection.
Why is my sha256 hash not working?
The hash must match the script text exactly, including whitespace and line breaks. Minifiers and templates often change it. It also has to be in single quotes inside the directive.
Why doesn't the nonce fix my onclick handler?
Nonces and hashes only apply to script elements. Inline event handlers need 'unsafe-inline' or 'unsafe-hashes', so move them to addEventListener.
Why is unsafe-inline ignored in my policy?
When a directive also has a nonce or hash, browsers ignore 'unsafe-inline'. It's kept only as a fallback for very old browsers.
What does 'strict-dynamic' do?
It trusts scripts loaded by a script that already has a valid nonce or hash, and ignores host allowlists, which makes nonce policies work with loaders and tag managers.
Published .