Content Security Policy (CSP) Utilities
The Content Security Policy (CSP) utilities in this project provide a structured way to define, manage, and inject CSP headers into HTTP responses. The implementation centers around a constant-based configuration system and a lazy nonce generation mechanism that ensures nonces are only included in headers if they are actually used in the response body.
Defining Policies with the CSP Enum
The CSP class in django.utils.csp is a StrEnum that serves as the source of truth for CSP directive values and header names. Using this enum instead of raw strings prevents common errors like missing quotes or typos in directive values.
Standard Constants
The enum includes standard CSP tokens that require single quotes in the final header:
CSP.SELF:"'self'"CSP.NONE:"'none'"CSP.UNSAFE_INLINE:"'unsafe-inline'"CSP.STRICT_DYNAMIC:"'strict-dynamic'"
Configuration Example
Policies are typically defined in settings.py using the SECURE_CSP dictionary. The keys are CSP directives, and the values are lists or sets of sources.
from django.utils.csp import CSP
SECURE_CSP = {
"default-src": [CSP.SELF],
"script-src": [CSP.SELF, CSP.NONCE],
"style-src": [CSP.SELF, CSP.UNSAFE_INLINE],
"base-uri": [CSP.SELF],
"frame-ancestors": [CSP.NONE],
}
The Nonce System
A core feature of this implementation is the "Lazy Nonce" system, which allows for cryptographically secure nonces to be injected into templates without forcing their inclusion in every response header.
LazyNonce Class
The LazyNonce class (in django/utils/csp.py) inherits from SimpleLazyObject. It wraps the generate_nonce function, which uses secrets.token_urlsafe(16) to create a secure string.
The "lazy" aspect is critical: the nonce is only generated if it is accessed (e.g., by being rendered in a template). LazyNonce implements __bool__ to return True only if the nonce has been evaluated:
class LazyNonce(SimpleLazyObject):
def __init__(self):
super().__init__(generate_nonce)
def __bool__(self):
return self._wrapped is not empty
The NONCE Sentinel
The CSP.NONCE constant is a special sentinel value: "<CSP_NONCE_SENTINEL>". It acts as a placeholder in the SECURE_CSP configuration. During the response phase, the middleware replaces this sentinel with the actual generated nonce.
Policy Construction and Optimization
The build_policy function in django/utils/csp.py is responsible for transforming the configuration dictionary into a valid CSP header string. It handles several important tasks:
- Consistency: If a
setis used for directive values, it sorts them to ensure a deterministic header string, which helps with browser caching. - Nonce Injection: It looks for the
CSP.NONCEsentinel. - Lazy Optimization: If
CSP.NONCEis present in the configuration but thenonceobject (aLazyNonce) evaluates toFalse(meaning it was never accessed in the template), the sentinel is removed entirely from the policy.
# Logic inside build_policy
if (has_sentinel := CSP.NONCE in values) and nonce:
# Replace sentinel with 'nonce-<actual_value>'
values = [f"'nonce-{nonce}'" if v == CSP.NONCE else v for v in values]
elif has_sentinel:
# Remove sentinel if nonce wasn't used
values = [v for v in values if v != CSP.NONCE]
Middleware and Template Integration
The CSP utilities are tied together by the ContentSecurityPolicyMiddleware and template tags.
Middleware Lifecycle
The ContentSecurityPolicyMiddleware (in django/middleware/csp.py) manages the nonce across the request/response cycle:
- Request Phase: It attaches a new
LazyNonceinstance torequest._csp_nonce. - Response Phase: It retrieves the policy from
settings.SECURE_CSP(or a view-specific override), callsbuild_policywith the request's nonce, and sets theContent-Security-Policyheader.
Template Usage
To use the nonce in a template, the {% csp_nonce_attr %} tag (which calls nonce_attr in django/utils/csp.py) is used. This tag renders the nonce="..." attribute only if a nonce is available in the context.
<!-- In a Django template -->
<script {% csp_nonce_attr %} src="/static/js/app.js"></script>
If the template contains this tag, the LazyNonce is evaluated, bool(nonce) becomes True, and build_policy subsequently includes the 'nonce-...' source in the response header. If no such tag is rendered, the header remains clean of unused nonce directives.