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.
For related production architectures and system implementations, explore these companion guides:
- Modern Web Security: Protecting User Privacy — Establish comprehensive security headers, cookie flags, and cross-origin isolation policies.
- Reverse Proxy Architecture with Nginx — Enforce strict security headers (HSTS, X-Content-Type-Options) at the Nginx reverse proxy layer.
- Enterprise OAuth2 & OIDC PKCE Authentication — Protect authentication endpoints against cross-site scripting (XSS) and token theft.
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.