Why CSP Blocks JavaScript
A web browser loads a page with an active Content-Security-Policy header. When the browser encounters an inline <script> tag, inline onclick event listener, third-party script, or eval() invocation that does not match the policy, the browser refuses to execute it and throws a security violation.
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-...'), or a nonce ('nonce-...') is required to enable inline execution.Why does Why CSP Blocks JavaScript occur?
Primary Root Cause
Content Security Policy exists specifically to neutralize Cross-Site Scripting (XSS). Because browsers cannot inherently distinguish between a legitimate inline script and an attacker-injected inline script, secure CSP policies ban all inline code execution by default.
When the HTML parser encounters a script, it checks the script-src (or default-src) directive. If the script is inline, it must match an exact sha256 hash or carry a matching nonce="...". If it is external (<script src="...">), its domain must be explicitly listed in script-src.
How to Confirm You Are Facing This Exact Issue
Console error: "Refused to execute inline script because it violates the following Content Security Policy directive: 'script-src 'self''".
Console error: "Refused to load the script 'https://cdn.example.com/app.js' because it violates the following directive: 'script-src 'self''".
Buttons with inline onclick="..." handlers fail to trigger any action.
Third-party analytics or widgets (Google Analytics, Stripe, Intercom) fail to load.
Explicit Diagnostic Reasoning
Analyze the exact console error message to pinpoint the missing directive:
"If the error specifically says "Either the 'unsafe-inline' keyword, a hash, or a nonce is required""
Your page contains an inline <script> block or inline onclick/onload attribute.
→ Add a nonce to the <script> tag or move the code into an external .js file.
"If the error says "Refused to load the script from https://...""
An external script domain is missing from your script-src policy.
→ Add that domain (e.g. https://js.stripe.com) to script-src in your CSP header.
"If the error says "Refused to evaluate a string as JavaScript because 'unsafe-eval' is not allowed""
A library is calling eval(), new Function(), or setTimeout with a string.
→ Update the library or replace eval() with JSON.parse().
Inspects your active CSP header to see which script-src directives are currently enforced.
Long-Term Engineering Solution
Implement a modern Nonce-Based Content Security Policy. The server generates a cryptographically random nonce on every HTTP request, inserts it into the CSP header, and attaches it to approved script tags.
Architectural Pattern
Deploy script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic'. This allows legitimate scripts with the nonce to load additional trusted dependencies without maintaining brittle domain whitelists.
How do you fix this step-by-step?
Generate a Cryptographic Nonce Per Request
In your backend server, generate a 16-byte random base64 string on every incoming request.
import crypto from 'crypto';
app.use((req, res, next) => {
// Generate unique nonce per request
res.locals.nonce = crypto.randomBytes(16).toString('base64');
// Set CSP header with nonce
res.setHeader(
'Content-Security-Policy',
`default-src 'self'; script-src 'self' 'nonce-${res.locals.nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline';`
);
next();
});Attach Nonce to Inline Script Tags
Pass the nonce into your HTML template and attach it to your script tags: <script nonce="<%= nonce %>">...</script>.
Refactor Inline Event Handlers
Replace inline handlers like <button onclick="save()"> with standard JavaScript addEventListener calls.
Whitelist Necessary Third-Party CDNs
If not using strict-dynamic, explicitly whitelist required third-party script providers: script-src 'self' https://js.stripe.com https://www.googletagmanager.com.
What are common misconceptions and mistakes when fixing this?
Adding "unsafe-inline" to your CSP to make the error go away
Perché fallisce: unsafe-inline completely turns off XSS protection. If an attacker injects a script via a comment or profile field, the browser will execute it without restriction.
✓ Alternativa corretta: Use per-request cryptographic nonces or script SHA-256 hashes.
Reusing a static, hardcoded nonce value across multiple requests
Perché fallisce: If the nonce is static, an attacker can simply inspect your page, copy the static nonce, and attach it to their injected malicious script.
✓ Alternativa corretta: Generate a fresh crypto.randomBytes(16) nonce on every single HTTP response.
Can this fix introduce security vulnerabilities?
CSP is the single most effective defense against Cross-Site Scripting (XSS). Bypassing it with unsafe directives exposes your users to session hijacking and credential theft.
Security Risks If Ignored
- •Using "unsafe-eval" allows attackers to execute arbitrary code via string evaluation.
- •Using permissive wildcards (script-src https: *) allows scripts to be loaded from any attacker-controlled server.
Hardening Best Practices
- ✓Use strict-dynamic to delegate trust from nonced scripts to dynamically loaded modules.
- ✓Deploy Content-Security-Policy-Report-Only first to log violations to an endpoint before enforcing blocks.
Fixing Inline Script Blocking with Nonces in Express / EJS
<!-- BROKEN: Inline script blocked by CSP -->
<script>
console.log('App initialized');
</script>
<button onclick="alert('Clicked!')">Click Me</button><!-- FIXED: Uses server-generated nonce and addEventListener -->
<script nonce="<%= nonce %>">
console.log('App initialized securely');
document.getElementById('action-btn').addEventListener('click', () => {
alert('Clicked securely!');
});
</script>
<button id="action-btn">Click Me</button>Frequently Asked Questions About Why CSP Blocks JavaScript
What is the difference between a CSP Nonce and a Hash?↓
A nonce is a dynamic random token generated per request; a hash is a static SHA-256 fingerprint of the exact script contents.
Hashes work well for static, unchanging inline scripts on static websites (like SSG builds). Nonces are ideal for dynamic server-rendered applications.
What is Content-Security-Policy-Report-Only?↓
A monitoring header that logs CSP violations to a report URI without actually blocking any scripts from executing.
Using Report-Only allows engineering teams to test new CSP policies in production and detect broken scripts before turning on strict blocking.