Introduction
Content Security Policy (CSP) is a browser security mechanism that prevents XSS attacks by controlling which resources can load and execute. When CSP blocks inline scripts, the console shows:
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-abc123...'), or a nonce ('nonce-...') is required.This commonly breaks analytics snippets, third-party widgets, and inline initialization code.
Symptoms
- Browser console shows "Refused to execute inline script" CSP violation
- Analytics scripts, chat widgets, or inline event handlers do not work
- CSP report endpoint receives
violated-directive: script-src-attrorscript-src-elem - Third-party scripts that inject inline code (Google Tag Manager, Hotjar) fail silently
- Page functionality breaks for features relying on inline
<script>blocks
Common Causes
- CSP
script-srcdirective does not include'unsafe-inline'(as it should not in production) - Inline
<script>tags in HTML are blocked by default under strict CSP - Event handler attributes (
onclick="...") in HTML are considered inline scripts - Third-party scripts that dynamically inject inline code are blocked
- Server sets CSP headers but template engines still output inline script blocks
Step-by-Step Fix
- 1.Use nonce-based CSP for inline scripts. Generate a unique nonce per request on the server:
- 2.```javascript
- 3.// Express.js example
- 4.const crypto = require('crypto');
- 5.app.use((req, res, next) => {
- 6.res.locals.nonce = crypto.randomBytes(16).toString('base64');
- 7.res.setHeader(
- 8.'Content-Security-Policy',
- 9.
script-src 'self' 'nonce-${res.locals.nonce}' https://www.google-analytics.com - 10.);
- 11.next();
- 12.});
- 13.
` - 14.Apply the nonce to inline script tags:
- 15.```html
- 16.<script nonce="<%= nonce %>">
- 17.window.dataLayer = window.dataLayer || [];
- 18.function gtag(){dataLayer.push(arguments);}
- 19.gtag('js', new Date());
- 20.gtag('config', 'GA-XXXXXXXXX');
- 21.</script>
- 22.
` - 23.Use hash-based CSP for static inline scripts. Calculate the SHA-256 hash of the script content:
- 24.```bash
- 25.echo -n "console.log('hello');" | openssl dgst -sha256 -binary | openssl base64 -A
- 26.
` - 27.Then include the hash in your CSP header:
- 28.
` - 29.Content-Security-Policy: script-src 'self' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='
- 30.
` - 31.Move inline scripts to external files as the most robust solution:
- 32.```html
- 33.<!-- Instead of inline script -->
- 34.<script src="/js/analytics-init.js"></script>
- 35.
` - 36.This eliminates the need for nonces or hashes entirely.
- 37.Handle event handler attributes by replacing them with addEventListener:
- 38.```html
- 39.<!-- BAD: Blocked by CSP -->
- 40.<button onclick="doSomething()">Click</button>
<!-- GOOD: Allowed by CSP --> <button id="action-btn">Click</button> <script nonce="<%= nonce %>"> document.getElementById('action-btn').addEventListener('click', doSomething); </script> ```
Prevention
- Never use
'unsafe-inline'in production CSP - it defeats the purpose of CSP - Implement CSP nonces using middleware that generates a unique nonce per request
- Use automated CSP tools like
csp-evaluatorfrom Google to audit your policy - Set up CSP report-only mode first to identify violations before enforcing:
`- Content-Security-Policy-Report-Only: script-src 'self' 'nonce-xyz'; report-uri /csp-report
`- Monitor your CSP report endpoint to detect new violations from third-party script updates
- Include CSP compliance checks in your code review process (no inline scripts, no onclick attributes)