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:

bash
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-attr or script-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-src directive 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. 1.Use nonce-based CSP for inline scripts. Generate a unique nonce per request on the server:
  2. 2.```javascript
  3. 3.// Express.js example
  4. 4.const crypto = require('crypto');
  5. 5.app.use((req, res, next) => {
  6. 6.res.locals.nonce = crypto.randomBytes(16).toString('base64');
  7. 7.res.setHeader(
  8. 8.'Content-Security-Policy',
  9. 9.script-src 'self' 'nonce-${res.locals.nonce}' https://www.google-analytics.com
  10. 10.);
  11. 11.next();
  12. 12.});
  13. 13.`
  14. 14.Apply the nonce to inline script tags:
  15. 15.```html
  16. 16.<script nonce="<%= nonce %>">
  17. 17.window.dataLayer = window.dataLayer || [];
  18. 18.function gtag(){dataLayer.push(arguments);}
  19. 19.gtag('js', new Date());
  20. 20.gtag('config', 'GA-XXXXXXXXX');
  21. 21.</script>
  22. 22.`
  23. 23.Use hash-based CSP for static inline scripts. Calculate the SHA-256 hash of the script content:
  24. 24.```bash
  25. 25.echo -n "console.log('hello');" | openssl dgst -sha256 -binary | openssl base64 -A
  26. 26.`
  27. 27.Then include the hash in your CSP header:
  28. 28.`
  29. 29.Content-Security-Policy: script-src 'self' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='
  30. 30.`
  31. 31.Move inline scripts to external files as the most robust solution:
  32. 32.```html
  33. 33.<!-- Instead of inline script -->
  34. 34.<script src="/js/analytics-init.js"></script>
  35. 35.`
  36. 36.This eliminates the need for nonces or hashes entirely.
  37. 37.Handle event handler attributes by replacing them with addEventListener:
  38. 38.```html
  39. 39.<!-- BAD: Blocked by CSP -->
  40. 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-evaluator from 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)