CSP

CSP Nonce vs Hash vs unsafe-inline โ€” When to Use Each

Updated April 2026

Reading this article? Verify your fix in real-time. Test your config live โ€” HttpFixer โ†’

Inline scripts are blocked by a strict CSP. You have three options to allow them โ€” but only two actually keep you safe.

ApproachSafe?Best forWorks for dynamic content?
unsafe-inlineโŒ NoDevelopment onlyYes โ€” but allows all inline scripts
Nonceโœ… YesServer-rendered pagesYes โ€” new nonce per request
Hashโœ… YesStatic inline scriptsNo โ€” 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
Compare headers between staging and production โ†’ Header Diff