CSP

CSP Nonce vs Hash vs unsafe-inline — When to Use Each

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