Hardening Web Security: Dynamic CSP Nonces, Subresource Integrity & Trusted Types in Django

Most websites neutralize their Content Security Policy (CSP) with 'unsafe-inline' to accommodate analytics and widgets. Master how to inject cryptographically secure per-request CSP nonces, enforce Subresource Integrity (SRI), and implement Trusted Types without breaking third-party scripts.

The 'unsafe-inline' Capitulation

Content Security Policy (CSP) is one of the most potent browser-native defenses against Cross-Site Scripting (XSS), data exfiltration, and clickjacking. When properly configured, a strict CSP restricts the domains from which scripts, styles, and media can load, and categorically forbids the execution of injected malicious inline JavaScript.

Yet, an alarming percentage of modern production applications deploy a neutered CSP containing 'unsafe-inline' and 'unsafe-eval'. Engineering teams make this concession because modern Single Page Applications, marketing analytics (Google Tag Manager, Segment), and interactive security widgets (Google reCAPTCHA, Cloudflare Turnstile) inject dynamic inline script tags. The moment 'unsafe-inline' is added, an attacker who successfully injects an XSS payload gains full script execution privileges.

Eliminating 'unsafe-inline' without breaking production third-party integrations requires three coordinated defenses: Dynamic Cryptographic Nonces, Subresource Integrity (SRI), and Trusted Types.

1. Per-Request CSP Nonce Generation in Django

A CSP nonce (number used once) is a cryptographically strong, base64-encoded random token generated uniquely for each HTTP request. The server transmits this token in the Content-Security-Policy response header, and developers attach the identical token to legitimate inline <script nonce="..."> tags. The browser permits execution only if the tag's nonce exactly matches the header nonce.

We implement a high-efficiency Django middleware that generates a 128-bit CSP nonce using Python's standard secrets module and attaches it to the request object:

# core/middleware/csp_nonce.py
import base64
import secrets

class CSPNonceMiddleware:
    # Generates a cryptographically strong per-request nonce
    # and injects strict Content-Security-Policy HTTP headers.
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # Generate 16 bytes (128 bits) of secure randomness
        raw_nonce = secrets.token_bytes(16)
        nonce = base64.b64encode(raw_nonce).decode('utf-8')
        request.csp_nonce = nonce

        response = self.get_response(request)

        # Assemble strict CSP directive
        policy = (
            f"default-src 'self'; "
            f"script-src 'self' 'nonce-{nonce}' 'strict-dynamic' https: 'unsafe-inline'; "
            f"style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com; "
            f"font-src 'self' https://cdnjs.cloudflare.com https://fonts.gstatic.com data:; "
            f"img-src 'self' data: https:; "
            f"connect-src 'self' https://api.devmanue.com https://www.google.com https://www.google-analytics.com; "
            f"frame-src https://www.google.com https://recaptcha.google.com; "
            f"frame-ancestors 'none'; "
            f"object-src 'none'; "
            f"base-uri 'none'; "
            f"form-action 'self';"
        )
        response["Content-Security-Policy"] = policy
        return response

2. The Power of 'strict-dynamic' for Third-Party Scripts

Notice the inclusion of 'strict-dynamic' in the script-src directive. This modern CSP3 feature is a game-changer: it tells compliant browsers that any script already authorized by a valid nonce is permitted to load and execute secondary dependency scripts dynamically. This completely resolves the perpetual struggle of integrating Google reCAPTCHA v3 or Tag Manager without whitelisting thousands of unpredictable CDN domains.

In your Django templates, simply inject the contextual nonce into all inline script tags:

<!-- base.html -->
<script nonce="{{ request.csp_nonce }}">
  window.DEVMANUE_CONFIG = {
    apiEndpoint: "/api/v1/",
    csrfToken: "{{ csrf_token }}"
  };
</script>

<!-- reCAPTCHA initialization using nonce -->
<script src="https://www.google.com/recaptcha/api.js?render={{ RECAPTCHA_SITE_KEY }}" 
        nonce="{{ request.csp_nonce }}" async defer></script>

3. Subresource Integrity (SRI) for CDN Assets

While nonces authenticate inline execution, external scripts loaded from public CDNs (like FontAwesome or jQuery) introduce supply-chain risks: if the CDN's servers are compromised, malicious code executes in your users' browsers. Subresource Integrity (SRI) ensures the browser verifies a SHA-384 or SHA-512 cryptographic hash before running external files:

<link rel="stylesheet" 
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" 
      integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" 
      crossorigin="anonymous" 
      referrerpolicy="no-referrer" />

Zero Disruption: Pair your reverse proxy configuration with zero-downtime Django deployments and Gunicorn atomic releases to prevent 502 Bad Gateway errors during rollouts.

Architectural Continuity & Deep Dives

For related production architectures and system implementations, explore these companion guides:

Backend Plugin Isolation: While CSP protects client-side browser execution, executing untrusted user code or custom webhooks on the server demands robust compute sandboxing. Explore zero-container isolation in Sandboxing Untrusted Code in Python with WebAssembly (Wasmtime): Zero-Container Secure Plugin Execution.

Production Takeaway

Deploying a per-request cryptographic nonce alongside 'strict-dynamic' allows engineering teams to dismantle 'unsafe-inline' permanently. Your web application gains comprehensive protection against DOM XSS and script injections while maintaining full compatibility with modern third-party tools.

All Insights
Chat on WhatsApp