Content Security Policy (CSP) Nonces & Hashes Guide
Cross-Site Scripting (XSS) remains one of the most prevalent attack vectors targeting modern web applications, allowing malicious actors to inject arbitrary JavaScript into trusted browser contexts.
While a basic Content Security Policy (CSP) restricts resource loading, robust XSS mitigation requires dynamic nonces (number-used-once) or cryptographic hashes to authorize inline scripts and style tags safely.
Core Mechanics of CSP Nonces and Hashes
Understanding how CSP authorizes inline execution requires examining the cryptographic mechanisms behind nonces and hashes:
- Cryptographic Nonces: A randomly generated, cryptographically secure base64-encoded string generated fresh on every HTTP response, added to the CSP header and matching inline script tags.
- Cryptographic Hashes: Pre-calculated SHA-256, SHA-384, or SHA-512 checksums of static inline script contents, allowing browsers to execute scripts only if their hash matches the CSP policy whitelist.
- Strict Dynamic Directives: Modern CSP implementations leverage
'strict-dynamic'to allow scripts trusted via nonces to safely load sub-dependencies without broad domain whitelisting.
CSP Nonces vs. Cryptographic Hashes
| Comparison Pillar | CSP Nonces (Number Used Once) | Cryptographic Script Hashes |
|---|---|---|
| Generation Timing | Generated dynamically on every single HTTP request / page load | Calculated statically during build time or deployment |
| Best Suited For | Dynamic template rendering and server-rendered HTML applications | Static inline scripts, bookmarklets, and unmodifiable third-party blocks |
| Maintenance Overhead | Requires middleware integration to inject fresh tokens per request | Requires re-computing hashes whenever inline script text changes |
Python Flask CSP Nonce Implementation Blueprint
Here is how to generate cryptographic nonces and enforce strict Content Security Policy headers in a Python Flask application:
import base64
import os
from flask import Flask, render_template_string, g, request
app = Flask(__name__)
@app.before_request
def generate_csp_nonce():
# 1. Generate a cryptographically secure random nonce per request
g.csp_nonce = base64.b64encode(os.urandom(16)).decode('utf-8')
@app.after_request
def apply_content_security_policy(response):
# 2. Construct strict CSP header using the request nonce
nonce = getattr(g, 'csp_nonce', '')
csp_policy = (
f"default-src 'self'; "
f"script-src 'self' 'nonce-{nonce}' 'strict-dynamic'; "
f"style-src 'self' 'nonce-{nonce}';"
)
response.headers['Content-Security-Policy'] = csp_policy
return response
@app.route('/')
ॉइड index():
# 3. Pass nonce into template rendering context
html_template = """
<!DOCTYPE html>
<html>
<head>
<title>CSP Secure Page</title>
<style nonce="{{ csp_nonce }}">
body { font-family: sans-serif; background: #f9f9f9; padding: 20px; }
</style>
</head>
<body>
<h1>Content Security Policy Secured</h1>
<script nonce="{{ csp_nonce }}">
console.log("Secure inline script executed successfully with nonce validation.");
</script>
</body>
</html>
"""
return render_template_string(html_template, csp_nonce=g.csp_nonce)
if __name__ == '__main__':
app.run(ssl_context='adhoc', debug=True)
Content Security Policy Best Practices
When deploying CSP nonces and hashes across production web applications, follow these engineering guidelines:
- Avoid Unsafe Directives: Never include
'unsafe-inline'or'unsafe-eval'in yourscript-srcdirective, as they completely negate nonce protections. - Use Report-Only Mode First: Deploy your policies using
Content-Security-Policy-Report-Onlyinitially to monitor policy violations and blocked assets without breaking production user flows. - Protect Against Nonce Prediction: Ensure nonces are generated using cryptographically secure pseudorandom number generators (like
os.urandom) with a minimum of 128 bits of entropy.