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 PillarCSP Nonces (Number Used Once)Cryptographic Script Hashes
Generation TimingGenerated dynamically on every single HTTP request / page loadCalculated statically during build time or deployment
Best Suited ForDynamic template rendering and server-rendered HTML applicationsStatic inline scripts, bookmarklets, and unmodifiable third-party blocks
Maintenance OverheadRequires middleware integration to inject fresh tokens per requestRequires 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:

Python
Generating secure cryptographic nonces and CSP headers in Python Flask
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 your script-src directive, as they completely negate nonce protections.
  • Use Report-Only Mode First: Deploy your policies using Content-Security-Policy-Report-Only initially 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.