Skip to main content

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:

  1. Consistency: If a set is used for directive values, it sorts them to ensure a deterministic header string, which helps with browser caching.
  2. Nonce Injection: It looks for the CSP.NONCE sentinel.
  3. Lazy Optimization: If CSP.NONCE is present in the configuration but the nonce object (a LazyNonce) evaluates to False (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 LazyNonce instance to request._csp_nonce.
  • Response Phase: It retrieves the policy from settings.SECURE_CSP (or a view-specific override), calls build_policy with the request's nonce, and sets the Content-Security-Policy header.

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.