CSP Nonce vs Hash vs unsafe-inline โ When to Use Each
Updated April 2026
Inline scripts are blocked by a strict CSP. You have three options to allow them โ but only two actually keep you safe.
| Approach | Safe? | Best for | Works for dynamic content? |
|---|---|---|---|
| unsafe-inline | โ No | Development only | Yes โ but allows all inline scripts |
| Nonce | โ Yes | Server-rendered pages | Yes โ new nonce per request |
| Hash | โ Yes | Static inline scripts | No โ content must match exactly |
unsafe-inline โ why it defeats CSP
Content-Security-Policy: script-src 'self' 'unsafe-inline'
This allows every inline script to run โ including any scripts an attacker manages to inject via XSS. It eliminates the "inline script blocked" console error, but it also eliminates all XSS protection. Never use in production.
Nonces โ the right approach for dynamic pages
# Server generates a random nonce each request
nonce = base64.b64encode(os.urandom(16)).decode()
# Header includes nonce
Content-Security-Policy: script-src 'self' 'nonce-{nonce}'
# HTML includes matching nonce attribute
<script nonce="{nonce}">console.log('allowed');</script>
<script>console.log('blocked โ no nonce');</script>
An injected script cannot know the nonce (it is random per request), so it is blocked. Your trusted scripts with the attribute are allowed.
Hashes โ for static inline scripts
# Compute SHA-256 hash of the exact script content
import hashlib, base64
content = "console.log('hello');"
hash_val = base64.b64encode(hashlib.sha256(content.encode()).digest()).decode()
# Header
Content-Security-Policy: script-src 'self' 'sha256-{hash_val}'
# HTML โ no attribute needed, content must match exactly
<script>console.log('hello');</script>
Change one character in the script and the hash no longer matches. Only use for scripts that never change.
Generate your CSP โ CSPFixer