CSP unsafe-inline vs Nonce vs Hash — Which to Use
You have a CSP that blocks inline scripts. You need inline scripts to run. You have three options — and only two of them actually keep you safe.
Option 1 — unsafe-inline (avoid in production)
Content-Security-Policy: script-src 'self' 'unsafe-inline'
This allows every inline script on your page to run — including any scripts an attacker manages to inject via XSS. It fixes the CSP error but defeats the entire purpose of having CSP. Use it in development only.
Option 2 — Nonces (best for dynamic content)
A nonce is a random base64 string generated per request. You set it on your inline scripts as an attribute, and include it in your CSP header. Only scripts with the matching nonce can run.
# Server generates nonce
nonce = base64.b64encode(os.urandom(16)).decode('utf-8')
# CSP header includes nonce
Content-Security-Policy: script-src 'self' 'nonce-{nonce}'
# HTML includes nonce attribute
<script nonce="{nonce}">
// This script is allowed
console.log('hello');
</script>
An attacker who injects a script tag cannot know the nonce (it changes every request), so their injected script is blocked. Your legitimate scripts with the nonce attribute run.
Option 3 — Hashes (best for static scripts)
If your inline script content never changes, compute its SHA-256 hash and add it to the CSP. Only scripts with matching content can run.
# Compute hash (Python example)
import hashlib, base64
script = "console.log('hello');"
digest = hashlib.sha256(script.encode()).digest()
hash_b64 = base64.b64encode(digest).decode()
# hash_b64 = 'abc123...'
# CSP header
Content-Security-Policy: script-src 'self' 'sha256-abc123...'
# HTML — no attribute needed, the content must match exactly
<script>console.log('hello');</script>
If the script content changes even by one character, the hash no longer matches and the script is blocked. This makes hashes unsuitable for any dynamically generated content.
Decision table
| Approach | Safe? | Best for | Changes per request? |
|---|---|---|---|
| unsafe-inline | ❌ No | Development only | N/A |
| Nonce | ✅ Yes | Dynamic inline scripts | Yes — new nonce each request |
| Hash | ✅ Yes | Static inline scripts | No — content must stay identical |