1
0
mirror of https://github.com/django/django.git synced 2025-06-30 07:49:19 +00:00

Fixed #15727 -- Added Content Security Policy (CSP) support.

This initial work adds a pair of settings to configure specific CSP
directives for enforcing or reporting policy violations, a new
`django.middleware.csp.ContentSecurityPolicyMiddleware` to apply the
appropriate headers to responses, and a context processor to support CSP
nonces in templates for safely inlining assets.

Relevant documentation has been added for the 6.0 release notes,
security overview, a new how-to page, and a dedicated reference section.

Thanks to the multiple reviewers for their precise and valuable feedback.

Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
This commit is contained in:
Rob Hudson 2025-05-03 10:01:58 -07:00 committed by nessita
parent 3f59711581
commit d63241ebc7
26 changed files with 1192 additions and 1 deletions

View File

@ -663,6 +663,12 @@ SECURE_REFERRER_POLICY = "same-origin"
SECURE_SSL_HOST = None SECURE_SSL_HOST = None
SECURE_SSL_REDIRECT = False SECURE_SSL_REDIRECT = False
##################
# CSP MIDDLEWARE #
##################
SECURE_CSP = {}
SECURE_CSP_REPORT_ONLY = {}
# RemovedInDjango70Warning: A transitional setting helpful in early adoption of # RemovedInDjango70Warning: A transitional setting helpful in early adoption of
# HTTPS as the default protocol in urlize and urlizetrunc when no protocol is # HTTPS as the default protocol in urlize and urlizetrunc when no protocol is
# provided. Set to True to assume HTTPS during the Django 6.x release cycle. # provided. Set to True to assume HTTPS during the Django 6.x release cycle.

View File

@ -141,6 +141,11 @@ E024 = Error(
W025 = Warning(SECRET_KEY_WARNING_MSG, id="security.W025") W025 = Warning(SECRET_KEY_WARNING_MSG, id="security.W025")
E026 = Error(
"The Content Security Policy setting '%s' must be a dictionary (got %r instead).",
id="security.E026",
)
def _security_middleware(): def _security_middleware():
return "django.middleware.security.SecurityMiddleware" in settings.MIDDLEWARE return "django.middleware.security.SecurityMiddleware" in settings.MIDDLEWARE
@ -281,3 +286,19 @@ def check_cross_origin_opener_policy(app_configs, **kwargs):
): ):
return [E024] return [E024]
return [] return []
@register(Tags.security)
def check_csp_settings(app_configs, **kwargs):
"""
Validate that CSP settings are properly configured when enabled.
Ensures both SECURE_CSP and SECURE_CSP_REPORT_ONLY are dictionaries.
"""
# CSP settings must be a dictionary or None.
return [
Error(E026.msg % (name, value), id=E026.id)
for name in ("SECURE_CSP", "SECURE_CSP_REPORT_ONLY")
if (value := getattr(settings, name, None)) is not None
and not isinstance(value, dict)
]

36
django/middleware/csp.py Normal file
View File

@ -0,0 +1,36 @@
from http import HTTPStatus
from django.conf import settings
from django.utils.csp import CSP, LazyNonce, build_policy
from django.utils.deprecation import MiddlewareMixin
def get_nonce(request):
return getattr(request, "_csp_nonce", None)
class ContentSecurityPolicyMiddleware(MiddlewareMixin):
def process_request(self, request):
request._csp_nonce = LazyNonce()
def process_response(self, request, response):
# In DEBUG mode, exclude CSP headers for specific status codes that
# trigger the debug view.
exempted_status_codes = {
HTTPStatus.NOT_FOUND,
HTTPStatus.INTERNAL_SERVER_ERROR,
}
if settings.DEBUG and response.status_code in exempted_status_codes:
return response
nonce = get_nonce(request)
for header, config in [
(CSP.HEADER_ENFORCE, settings.SECURE_CSP),
(CSP.HEADER_REPORT_ONLY, settings.SECURE_CSP_REPORT_ONLY),
]:
# If headers are already set on the response, don't overwrite them.
# This allows for views to set their own CSP headers as needed.
if config and header not in response:
response.headers[str(header)] = build_policy(config, nonce)
return response

View File

@ -10,6 +10,7 @@ of a DjangoTemplates backend and used by RequestContext.
import itertools import itertools
from django.conf import settings from django.conf import settings
from django.middleware.csp import get_nonce
from django.middleware.csrf import get_token from django.middleware.csrf import get_token
from django.utils.functional import SimpleLazyObject, lazy from django.utils.functional import SimpleLazyObject, lazy
@ -87,3 +88,10 @@ def media(request):
def request(request): def request(request):
return {"request": request} return {"request": request}
def csp(request):
"""
Add the CSP nonce to the context.
"""
return {"csp_nonce": get_nonce(request)}

110
django/utils/csp.py Normal file
View File

@ -0,0 +1,110 @@
import secrets
from enum import StrEnum
from django.utils.functional import SimpleLazyObject, empty
class CSP(StrEnum):
"""
Content Security Policy constants for directive values and special tokens.
These constants represent:
1. Standard quoted string values from the CSP spec (e.g., 'self', 'unsafe-inline')
2. Special placeholder tokens (NONCE) that get replaced by the middleware
Using this enum instead of raw strings provides better type checking,
autocompletion, and protection against common mistakes like:
- Typos (e.g., 'noone' instead of 'none')
- Missing quotes (e.g., ["self"] instead of ["'self'"])
- Inconsistent quote styles (e.g., ["'self'", "\"unsafe-inline\""])
Example usage in Django settings:
SECURE_CSP = {
"default-src": [CSP.NONE],
"script-src": [CSP.SELF, CSP.NONCE],
}
"""
# HTTP Headers.
HEADER_ENFORCE = "Content-Security-Policy"
HEADER_REPORT_ONLY = "Content-Security-Policy-Report-Only"
# Standard CSP directive values.
NONE = "'none'"
REPORT_SAMPLE = "'report-sample'"
SELF = "'self'"
STRICT_DYNAMIC = "'strict-dynamic'"
UNSAFE_EVAL = "'unsafe-eval'"
UNSAFE_HASHES = "'unsafe-hashes'"
UNSAFE_INLINE = "'unsafe-inline'"
WASM_UNSAFE_EVAL = "'wasm-unsafe-eval'"
# Special placeholder that gets replaced by the middleware.
# The value itself is arbitrary and should not be mistaken for a real nonce.
NONCE = "<CSP_NONCE_SENTINEL>"
class LazyNonce(SimpleLazyObject):
"""
Lazily generates a cryptographically secure nonce string, for use in CSP headers.
The nonce is only generated when first accessed (e.g., via string
interpolation or inside a template).
The nonce will evaluate as `True` if it has been generated, and `False` if
it has not. This is useful for third-party Django libraries that want to
support CSP without requiring it.
Example Django template usage with context processors enabled:
<script{% if csp_nonce %} nonce="{{ csp_nonce }}"...{% endif %}>
The `{% if %}` block will only render if the nonce has been evaluated elsewhere.
"""
def __init__(self):
super().__init__(self._generate)
def _generate(self):
return secrets.token_urlsafe(16)
def __bool__(self):
return self._wrapped is not empty
def build_policy(config, nonce=None):
policy = []
for directive, values in config.items():
if values in (None, False):
continue
if values is True:
rendered_value = ""
else:
if isinstance(values, set):
# Sort values for consistency, preventing cache invalidation
# between requests and ensuring reliable browser caching.
values = sorted(values)
elif not isinstance(values, list | tuple):
values = [values]
# Replace the nonce sentinel with the actual nonce values, if the
# sentinel is found and a nonce is provided. Otherwise, remove it.
if (has_sentinel := CSP.NONCE in values) and nonce:
values = [f"'nonce-{nonce}'" if v == CSP.NONCE else v for v in values]
elif has_sentinel:
values = [v for v in values if v != CSP.NONCE]
if not values:
continue
rendered_value = " ".join(values)
policy.append(f"{directive} {rendered_value}".rstrip())
return "; ".join(policy)

100
docs/howto/csp.txt Normal file
View File

@ -0,0 +1,100 @@
===========================================
How to use Django's Content Security Policy
===========================================
.. _csp-config:
Basic config
============
To enable Content Security Policy (CSP) in your Django project:
1. Add the CSP middleware to your :setting:`MIDDLEWARE` setting::
MIDDLEWARE = [
# ...
"django.middleware.csp.ContentSecurityPolicyMiddleware",
# ...
]
2. Configure the CSP policies in your ``settings.py`` using either
:setting:`SECURE_CSP` or :setting:`SECURE_CSP_REPORT_ONLY` (or both). The
:ref:`CSP Settings docs <csp-settings>` provide more details about the
differences between these two::
from django.utils.csp import CSP
# To enforce a CSP policy:
SECURE_CSP = {
"default-src": [CSP.SELF],
# Add more directives to be enforced.
}
# Or for report-only mode:
SECURE_CSP_REPORT_ONLY = {
"default-src": [CSP.SELF],
# Add more directives as needed.
"report-uri": "/path/to/reports-endpoint/",
}
.. _csp-nonce-config:
Nonce config
============
To use nonces in your CSP policy, beside the basic config, you need to:
1. Include the :attr:`~django.utils.csp.CSP.NONCE` placeholder value in the CSP
settings. This only applies to ``script-src`` or ``style-src`` directives::
from django.utils.csp import CSP
SECURE_CSP = {
"default-src": [CSP.SELF],
# Allow self-hosted scripts and script tags with matching `nonce` attr.
"script-src": [CSP.SELF, CSP.NONCE],
# Example of the less secure 'unsafe-inline' option.
"style-src": [CSP.SELF, CSP.UNSAFE_INLINE],
}
2. Add the :func:`~django.template.context_processors.csp` context processor to
your :setting:`TEMPLATES` setting. This makes the generated nonce value
available in the Django templates as the ``csp_nonce`` context variable::
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"OPTIONS": {
"context_processors": [
# ...
"django.template.context_processors.csp",
],
},
},
]
3. In your templates, add the ``nonce`` attribute to the relevant inline
``<style>`` or ``<script>`` tags, using the ``csp_nonce`` context variable:
.. code-block:: html+django
<style nonce="{{ csp_nonce }}">
/* These inline styles will be allowed. */
</style>
<script nonce="{{ csp_nonce }}">
// This inline JavaScript will be allowed.
</script>
.. admonition:: Caching and Nonce Reuse
The :class:`~django.middleware.csp.ContentSecurityPolicyMiddleware`
automatically handles generating a unique nonce and inserting the
appropriate ``nonce-<value>`` source expression into the
``Content-Security-Policy`` (or ``Content-Security-Policy-Report-Only``)
header when the nonce is used in a template.
To ensure correct behavior, make sure both the HTML and the header are
generated within the same request and not served from cache. See the
reference documentation on :ref:`csp-nonce` for implementation details and
important caching considerations.

View File

@ -57,6 +57,7 @@ Other guides
:maxdepth: 1 :maxdepth: 1
auth-remote-user auth-remote-user
csp
csrf csrf
custom-file-storage custom-file-storage
custom-management-commands custom-management-commands

View File

@ -251,6 +251,7 @@ applications and Django provides multiple protection tools and mechanisms:
* :doc:`Cross Site Request Forgery protection <ref/csrf>` * :doc:`Cross Site Request Forgery protection <ref/csrf>`
* :doc:`Cryptographic signing <topics/signing>` * :doc:`Cryptographic signing <topics/signing>`
* :ref:`Security Middleware <security-middleware>` * :ref:`Security Middleware <security-middleware>`
* :doc:`Content Security Policy <ref/csp>`
Internationalization and localization Internationalization and localization
===================================== =====================================

View File

@ -568,6 +568,8 @@ The following checks are run if you use the :option:`check --deploy` option:
``'django-insecure-'`` indicating that it was generated automatically by ``'django-insecure-'`` indicating that it was generated automatically by
Django. Please generate a long and random value, otherwise many of Django's Django. Please generate a long and random value, otherwise many of Django's
security-critical features will be vulnerable to attack. security-critical features will be vulnerable to attack.
* **security.E026**: The CSP setting ``<SETTING_NAME>`` must be a dictionary
(got ``<value>`` instead).
The following checks verify that your security-related settings are correctly The following checks verify that your security-related settings are correctly
configured: configured:

210
docs/ref/csp.txt Normal file
View File

@ -0,0 +1,210 @@
=======================
Content Security Policy
=======================
.. versionadded:: 6.0
.. module:: django.middleware.csp
:synopsis: Middleware for Content Security Policy headers
Content Security Policy (CSP) is a web security standard that helps prevent
content injection attacks by restricting the sources from which content can be
loaded. It plays an important role in a comprehensive :ref:`security strategy
<security-csp>`.
For configuration instructions in a Django project, see the :ref:`Using CSP
<csp-config>` documentation. For an HTTP guide about CSP, see the `MDN Guide on
CSP <https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP>`_.
.. _csp-overview:
Overview
========
The `Content-Security-Policy specification <https://www.w3.org/TR/CSP3/>`_
defines two complementary headers:
* ``Content-Security-Policy``: Enforces the CSP policy, blocking content that
violates the defined directives.
* ``Content-Security-Policy-Report-Only``: Reports CSP violations without
blocking content, allowing for non-intrusive testing.
Each policy is composed of one or more directives and their values, which
together instruct the browser on how to handle specific types of content.
When the :class:`~django.middleware.csp.ContentSecurityPolicyMiddleware` is
enabled, Django automatically builds and attaches the appropriate headers to
each response based on the configured :ref:`settings <csp-settings>`, unless
they have already been set by another layer.
.. _csp-settings:
Settings
========
The :class:`~django.middleware.csp.ContentSecurityPolicyMiddleware` is
configured using the following settings:
* :setting:`SECURE_CSP`: defines the **enforced Content Security Policy**.
* :setting:`SECURE_CSP_REPORT_ONLY`: defines a **report-only Content Security Policy**.
.. admonition:: These settings can be used independently or together
* Use :setting:`SECURE_CSP` alone to enforce a policy that has already been
tested and verified.
* Use :setting:`SECURE_CSP_REPORT_ONLY` on its own to evaluate a new policy
without disrupting site behavior. This mode does not block violations, it
only logs them. It's useful for testing and monitoring, but provides no
protection against active threats.
* Use *both* to maintain an enforced baseline while experimenting with
changes. Even for well-established policies, continuing to collect reports
reports can help detect regressions, unexpected changes in behavior, or
potential tampering in production environments.
.. _csp-reports:
Policy violation reports
========================
When a CSP violation occurs, browsers typically log details to the developer
console, providing immediate feedback during development. To also receive these
reports programmatically, the policy must include a `reporting directive
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy#reporting_directives>`_
such as ``report-uri`` that specifies where violation data should be sent.
Django supports configuring these directives via the
:setting:`SECURE_CSP_REPORT_ONLY` settings, but reports will only be issued by
the browser if the policy explicitly includes a valid reporting directive.
Django does not provide built-in functionality to receive, store, or process
violation reports. To collect and analyze them, you must implement your own
reporting endpoint or integrate with a third-party monitoring service.
.. _csp-constants:
CSP constants
=============
Django provides predefined constants representing common CSP source expression
keywords such as ``'self'``, ``'none'``, and ``'unsafe-inline'``. These
constants are intended for use in the directive values defined in the settings.
They are available through the :class:`~django.utils.csp.CSP` enum, and using
them is recommended over raw strings. This helps avoid common mistakes such as
typos, improper quoting, or inconsistent formatting, and ensures compliance
with the CSP specification.
.. module:: django.utils.csp
:synopsis: Constants for Content Security Policy
.. class:: CSP
Enum providing standardized constants for common CSP source expressions.
.. attribute:: NONE
Represents ``'none'``. Blocks loading resources for the given directive.
.. attribute:: REPORT_SAMPLE
Represents ``'report-sample'``. Instructs the browser to include a sample
of the violating code in reports. Note that this may expose sensitive
data.
.. attribute:: SELF
Represents ``'self'``. Allows loading resources from the same origin
(same scheme, host, and port).
.. attribute:: STRICT_DYNAMIC
Represents ``'strict-dynamic'``. Allows execution of scripts loaded by a
trusted script (e.g., one with a valid nonce or hash), without needing
``'unsafe-inline'``.
.. attribute:: UNSAFE_EVAL
Represents ``'unsafe-eval'``. Allows use of ``eval()`` and similar
JavaScript functions. Strongly discouraged.
.. attribute:: UNSAFE_HASHES
Represents ``'unsafe-hashes'``. Allows inline event handlers and some
``javascript:`` URIs when their content hashes match a policy rule.
Requires CSP Level 3+.
.. attribute:: UNSAFE_INLINE
Represents ``'unsafe-inline'``. Allows execution of inline scripts,
styles, and ``javascript:`` URLs. Generally discouraged, especially for
scripts.
.. attribute:: WASM_UNSAFE_EVAL
Represents ``'wasm-unsafe-eval'``. Permits compilation and execution of
WebAssembly code without enabling ``'unsafe-eval'`` for scripts.
.. attribute:: NONCE
Django-specific placeholder value (``"<CSP_NONCE_SENTINEL>"``) used in
``script-src`` or ``style-src`` directives to activate nonce-based CSP.
This string is replaced at runtime by the
:class:`~django.middleware.csp.ContentSecurityPolicyMiddleware` with a
secure, random nonce that is generated for each request. See detailed
explanation in :ref:`csp-nonce`.
.. _csp-nonce:
Nonce usage
===========
A CSP nonce ("number used once") is a unique, random value generated per HTTP
response. Django supports nonces as a secure way to allow specific inline
``<script>`` or ``<style>`` elements to execute without relying on
``'unsafe-inline'``.
Nonces are enabled by including the special placeholder
:attr:`~django.utils.csp.CSP.NONCE` in the relevant directive(s) of your
:ref:`CSP settings <csp-settings>`, such as ``script-src`` or ``style-src``.
When present, the
:class:`~django.middleware.csp.ContentSecurityPolicyMiddleware`
will generate a nonce and insert the corresponding ``nonce-<value>`` source
expression into the CSP header.
To use this nonce in templates, the
:func:`~django.template.context_processors.csp` context processor needs to be
enabled. It adds a ``csp_nonce`` variable to the template context, allowing
inline elements to include a matching ``nonce={{ csp_nonce }}`` attribute in
inline scripts or styles.
The browser will only execute inline elements that include a ``nonce=<value>``
attribute matching the one specified in the ``Content-Security-Policy`` (or
``Content-Security-Policy-Report-Only``) header. This mechanism provides
fine-grained control over which inline code is allowed to run.
If a template includes ``{{ csp_nonce }}`` but the policy does not include
:attr:`~django.utils.csp.CSP.NONCE`, the HTML will include a nonce attribute,
but the header will lack the required source expression. In this case, the
browser will block the inline script or style (or report it for report-only
configurations).
Nonce generation and caching
----------------------------
Django's nonce generation is **lazy**: the middleware only generates a nonce if
``{{ csp_nonce }}`` is accessed during template rendering. This avoids
unnecessary work for pages that do not use nonces.
However, because nonces must be unique per request, extra care is needed when
using full-page caching (e.g., Django's cache middleware, CDN caching). Serving
cached responses with previously generated nonces may result in reuse across
users and requests. Although such responses may still appear to work (since the
nonce in the CSP header and HTML content match), reuse defeats the purpose of
the nonce and weakens security.
To ensure nonce-based policies remain effective:
* Avoid caching full responses that include ``{{ csp_nonce }}``.
* If caching is necessary, use a strategy that injects a fresh nonce on each
request, or consider refactoring your application to avoid inline scripts and
styles altogether.

View File

@ -10,6 +10,7 @@ API Reference
class-based-views/index class-based-views/index
clickjacking clickjacking
contrib/index contrib/index
csp
csrf csrf
databases databases
django-admin django-admin

View File

@ -607,6 +607,26 @@ You can add Cross Site Request Forgery protection to individual views using the
Simple :doc:`clickjacking protection via the X-Frame-Options header </ref/clickjacking/>`. Simple :doc:`clickjacking protection via the X-Frame-Options header </ref/clickjacking/>`.
Content Security Policy middleware
----------------------------------
.. currentmodule:: django.middleware.csp
.. class:: ContentSecurityPolicyMiddleware
.. versionadded:: 6.0
Adds support for Content Security Policy (CSP), which helps mitigate risks such
as Cross-Site Scripting (XSS) and data injection attacks by controlling the
sources of content that can be loaded in the browser. See the
:ref:`csp-overview` documentation for details on configuring policies.
This middleware sets the following headers on the response depending on the
available settings:
* ``Content-Security-Policy``, based on :setting:`SECURE_CSP`.
* ``Content-Security-Policy-Report-Only``, based on :setting:`SECURE_CSP_REPORT_ONLY`.
.. _middleware-ordering: .. _middleware-ordering:
Middleware ordering Middleware ordering
@ -691,6 +711,12 @@ Here are some hints about the ordering of various Django middleware classes:
After any middleware that modifies the ``Vary`` header: that header is used After any middleware that modifies the ``Vary`` header: that header is used
to pick a value for the cache hash-key. to pick a value for the cache hash-key.
#. :class:`~django.middleware.csp.ContentSecurityPolicyMiddleware`
Can be placed near the bottom, but ensure any middleware that accesses
:ref:`csp_nonce <csp-nonce>` is positioned after it, so the nonce is
properly included in the response header.
#. :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware` #. :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`
Should be near the bottom as it's a last-resort type of middleware. Should be near the bottom as it's a last-resort type of middleware.

View File

@ -2363,6 +2363,94 @@ Unless set to ``None``, the
:ref:`cross-origin-opener-policy` header on all responses that do not already :ref:`cross-origin-opener-policy` header on all responses that do not already
have it to the value provided. have it to the value provided.
.. setting:: SECURE_CSP
``SECURE_CSP``
--------------
.. versionadded:: 6.0
Default: ``{}``
This setting defines the directives used by the
:class:`~django.middleware.csp.ContentSecurityPolicyMiddleware`, which
generates and adds a :ref:`Content-Security-Policy <csp-overview>` (CSP) header
to all responses that do not already include one.
The ``Content-Security-Policy`` header instructs browsers to restrict which
resources a page is allowed to load. A properly configured CSP can block
content that violates defined rules, helping prevent cross-site scripting (XSS)
and other content injection attacks by explicitly declaring trusted sources for
content such as scripts, styles, images, fonts, and more.
The setting must be a mapping (typically a dictionary) of directive names to
their values. Each key should be a valid CSP directive such as ``default-src``
or ``script-src``. The corresponding value can be a list, tuple, or set of
source expressions or URLs to allow for that directive. If a set is used, it
will be automatically sorted to ensure consistent output in the generated
headers.
This example illustrates the expected structure, using the constants defined in
:ref:`csp-constants`::
from django.utils.csp import CSP
SECURE_CSP = {
"default-src": [CSP.SELF],
"img-src": ["data:", CSP.SELF, "https://images.example.com"],
"frame-src": [CSP.NONE],
}
.. admonition:: Directives validation
Django's CSP middleware helps construct and send the appropriate header
based on your settings, but it does **not validate** that the directives and
values conform to the CSP specification. It is your responsibility to ensure
that the configuration is syntactically and semantically correct. Use
browser developer tools or external CSP validators during development.
For a list of available directives and their values, refer to the `MDN
documentation on CSP directives
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives>`_.
.. setting:: SECURE_CSP_REPORT_ONLY
``SECURE_CSP_REPORT_ONLY``
--------------------------
.. versionadded:: 6.0
Default: ``{}``
This setting is just like :setting:`SECURE_CSP`, but instead of enforcing the
policy, it instructs the
:class:`~django.middleware.csp.ContentSecurityPolicyMiddleware` to apply a
``Content-Security-Policy-Report-Only`` header to responses, which allows
browsers to monitor and report policy violations without blocking content. This
is useful for testing and refining a policy before enforcement.
Most browsers log CSP violations to the developer console and can optionally
send them to a reporting endpoint. To collect these reports, the ``report-uri``
directive must be defined (see :ref:`csp-reports` for more details).
As noted in the `MDN documentation on Content-Security-Policy-Report-Only
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only>`_,
the ``report-uri`` directive must be specified for reports to be sent;
otherwise, the header has no reporting effect (other than logging to the
browser's developer tools console).
Following the example from the :setting:`SECURE_CSP` setting::
from django.utils.csp import CSP
SECURE_CSP_REPORT_ONLY = {
"default-src": [CSP.SELF],
"img-src": ["data:", CSP.SELF, "https://images.example.com"],
"frame-src": [CSP.NONE],
"report-uri": "/my-site/csp/reports/",
}
.. setting:: SECURE_HSTS_INCLUDE_SUBDOMAINS .. setting:: SECURE_HSTS_INCLUDE_SUBDOMAINS
``SECURE_HSTS_INCLUDE_SUBDOMAINS`` ``SECURE_HSTS_INCLUDE_SUBDOMAINS``
@ -3749,6 +3837,8 @@ HTTP
* :setting:`SECURE_CONTENT_TYPE_NOSNIFF` * :setting:`SECURE_CONTENT_TYPE_NOSNIFF`
* :setting:`SECURE_CROSS_ORIGIN_OPENER_POLICY` * :setting:`SECURE_CROSS_ORIGIN_OPENER_POLICY`
* :setting:`SECURE_CSP`
* :setting:`SECURE_CSP_REPORT_ONLY`
* :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS` * :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS`
* :setting:`SECURE_HSTS_PRELOAD` * :setting:`SECURE_HSTS_PRELOAD`
* :setting:`SECURE_HSTS_SECONDS` * :setting:`SECURE_HSTS_SECONDS`

View File

@ -802,6 +802,18 @@ This processor adds a token that is needed by the :ttag:`csrf_token` template
tag for protection against :doc:`Cross Site Request Forgeries tag for protection against :doc:`Cross Site Request Forgeries
</ref/csrf>`. </ref/csrf>`.
``django.template.context_processors.csp``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. function:: csp(request)
.. versionadded:: 6.0
If this processor is enabled, every ``RequestContext`` will contain a variable
``csp_nonce``, providing a securely generated, request-specific nonce suitable
for use under a Content Security Policy. See :ref:`CSP nonce usage <csp-nonce>`
for details.
``django.template.context_processors.request`` ``django.template.context_processors.request``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -37,6 +37,43 @@ compatible with Django 6.0.
What's new in Django 6.0 What's new in Django 6.0
======================== ========================
Content Security Policy support
-------------------------------
Built-in support for the :ref:`Content Security Policy (CSP) <security-csp>`
standard is now available, making it easier to protect web applications against
content injection attacks such as cross-site scripting (XSS). CSP allows
declaring trusted sources of content by giving browsers strict rules about
which scripts, styles, images, or other resources can be loaded.
CSP policies can now be enforced or monitored directly using built-in tools:
headers are added via the
:class:`~django.middleware.csp.ContentSecurityPolicyMiddleware`, nonces are
supported through the :func:`~django.template.context_processors.csp` context
processor, and policies are configured using the :setting:`SECURE_CSP` and
:setting:`SECURE_CSP_REPORT_ONLY` settings.
These settings accept Python dictionaries and support Django-provided constants
for clarity and safety. For example::
from django.utils.csp import CSP
SECURE_CSP = {
"default-src": [CSP.SELF],
"script-src": [CSP.SELF, CSP.NONCE],
"img-src": [CSP.SELF, "https:"],
}
The resulting ``Content-Security-Policy`` header would be set to:
.. code-block:: text
default-src 'self'; script-src 'self' 'nonce-SECRET'; img-src 'self' https:
To get started, follow the :doc:`CSP how-to guide </howto/csp>`. For in-depth
guidance, see the :ref:`CSP security overview <security-csp>` and the
:doc:`reference docs </ref/csp>`.
Minor features Minor features
-------------- --------------

View File

@ -310,6 +310,7 @@ needsinfo
německy německy
nginx nginx
noding noding
nonces
nonnegative nonnegative
nullable nullable
OAuth OAuth

View File

@ -286,6 +286,61 @@ User-uploaded content
.. _same-origin policy: https://en.wikipedia.org/wiki/Same-origin_policy .. _same-origin policy: https://en.wikipedia.org/wiki/Same-origin_policy
.. _security-csp:
Content Security Policy
=======================
.. versionadded:: 6.0
Content Security Policy (CSP) is a browser security mechanism that helps
protect web applications against attacks such as cross-site scripting (XSS) and
other content injection attacks.
CSP allows web applications to define which sources of content are trusted,
instructing the browser to load, execute, or render resources only from those
sources. This effectively creates an allowlist of content origins, reducing the
risk of malicious code execution.
Key benefits of enabling CSP include:
1. Mitigating XSS attacks by blocking inline scripts and restricting external
script loading.
2. Controlling which external resources (e.g., images, fonts, stylesheets) can
be loaded.
3. Preventing unwanted framing of your site to protect against clickjacking.
4. Reporting violations to a specified endpoint, enabling monitoring and
debugging.
For configuration instructions, see the :ref:`Using CSP <csp-config>`
documentation, and refer to the :ref:`CSP overview <csp-overview>` for details
on directives and settings.
Limitations and considerations
------------------------------
While CSP is a powerful security mechanism, it's important to understand its
limitations and implications, particularly when used in Django:
* Policy exclusion risks: Avoid excluding specific paths or responses from
CSP protection. Due to the browsers same-origin policy, a vulnerability on
an unprotected page (e.g., one allowing arbitrary script injection) may be
leveraged to attack protected pages. Excluding *any* route can significantly
weaken the site's overall CSP protection.
* Performance overhead: Although typically negligible, CSP adds some processing
overhead. Nonce generation involves secure randomness for each applicable
request. For high-traffic applications or resource-constrained environments,
measure the performance impact accordingly.
* Browser support: While CSP Levels 1 and 2 are widely supported, newer
directives (CSP Level 3+) or complex policy behaviors may vary across
browsers. Test your policy across the environments you intend to support.
Despite these limitations, CSP remains an important and recommended security
layer for web applications. Understanding its constraints will help you design
a more effective and reliable deployment.
.. _additional-security-topics: .. _additional-security-topics:
Additional security topics Additional security topics

View File

@ -1,3 +1,5 @@
import itertools
from django.conf import settings from django.conf import settings
from django.core.checks.messages import Error, Warning from django.core.checks.messages import Error, Warning
from django.core.checks.security import base, csrf, sessions from django.core.checks.security import base, csrf, sessions
@ -678,3 +680,54 @@ class CheckCrossOriginOpenerPolicyTest(SimpleTestCase):
) )
def test_with_invalid_coop(self): def test_with_invalid_coop(self):
self.assertEqual(base.check_cross_origin_opener_policy(None), [base.E024]) self.assertEqual(base.check_cross_origin_opener_policy(None), [base.E024])
class CheckSecureCSPTests(SimpleTestCase):
"""Tests for the CSP settings check function."""
def test_secure_csp_allowed_values(self):
"""Check should pass when both CSP settings are None or dicts."""
allowed_values = (None, {}, {"key": "value"})
combinations = itertools.product(allowed_values, repeat=2)
for csp_value, csp_report_only_value in combinations:
with (
self.subTest(
csp_value=csp_value, csp_report_only_value=csp_report_only_value
),
self.settings(
SECURE_CSP=csp_value, SECURE_CSP_REPORT_ONLY=csp_report_only_value
),
):
errors = base.check_csp_settings(None)
self.assertEqual(errors, [])
def test_secure_csp_invalid_values(self):
"""Check should fail when either CSP setting is not a dict."""
for value in (
False,
True,
0,
42,
"",
"not-a-dict",
set(),
{"a", "b"},
[],
[1, 2, 3, 4],
):
with self.subTest(value=value):
csp_error = Error(
base.E026.msg % ("SECURE_CSP", value), id=base.E026.id
)
with self.settings(SECURE_CSP=value):
errors = base.check_csp_settings(None)
self.assertEqual(errors, [csp_error])
csp_report_only_error = Error(
base.E026.msg % ("SECURE_CSP_REPORT_ONLY", value), id=base.E026.id
)
with self.settings(SECURE_CSP_REPORT_ONLY=value):
errors = base.check_csp_settings(None)
self.assertEqual(errors, [csp_report_only_error])
with self.settings(SECURE_CSP=value, SECURE_CSP_REPORT_ONLY=value):
errors = base.check_csp_settings(None)
self.assertEqual(errors, [csp_error, csp_report_only_error])

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CSP Nonce Test</title>
</head>
<body>
<h1>CSP Nonce Test</h1>
<p>CSP Nonce is present: {{ csp_nonce }}</p>
<script nonce="{{ csp_nonce }}">
console.log("This script is allowed to run due to the nonce.");
</script>
<script>
console.log("This script might be blocked by CSP if a nonce is required.");
</script>
</body>
</html>

View File

@ -2,7 +2,8 @@
Tests for Django's bundled context processors. Tests for Django's bundled context processors.
""" """
from django.test import SimpleTestCase, TestCase, override_settings from django.test import SimpleTestCase, TestCase, modify_settings, override_settings
from django.utils.csp import CSP
@override_settings( @override_settings(
@ -96,3 +97,65 @@ class DebugContextProcessorTests(TestCase):
self.assertContains(response, "Third query list: 2") self.assertContains(response, "Third query list: 2")
# Check queries for DB connection 'other' # Check queries for DB connection 'other'
self.assertContains(response, "Fourth query list: 3") self.assertContains(response, "Fourth query list: 3")
@override_settings(
ROOT_URLCONF="context_processors.urls",
TEMPLATES=[
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.csp",
],
},
}
],
MIDDLEWARE=[
"django.middleware.csp.ContentSecurityPolicyMiddleware",
],
SECURE_CSP={
"script-src": [CSP.SELF, CSP.NONCE],
},
)
class CSPContextProcessorTests(TestCase):
"""
Tests for the django.template.context_processors.csp_nonce processor.
"""
def test_csp_nonce_in_context(self):
response = self.client.get("/csp_nonce/")
self.assertIn("csp_nonce", response.context)
@modify_settings(
MIDDLEWARE={"remove": "django.middleware.csp.ContentSecurityPolicyMiddleware"}
)
def test_csp_nonce_in_context_no_middleware(self):
response = self.client.get("/csp_nonce/")
self.assertIn("csp_nonce", response.context)
def test_csp_nonce_in_header(self):
response = self.client.get("/csp_nonce/")
self.assertIn(CSP.HEADER_ENFORCE, response.headers)
csp_header = response.headers[CSP.HEADER_ENFORCE]
nonce = response.context["csp_nonce"]
self.assertIn(f"'nonce-{nonce}'", csp_header)
def test_different_nonce_per_request(self):
response1 = self.client.get("/csp_nonce/")
response2 = self.client.get("/csp_nonce/")
self.assertNotEqual(
response1.context["csp_nonce"],
response2.context["csp_nonce"],
)
def test_csp_nonce_in_template(self):
response = self.client.get("/csp_nonce/")
nonce = response.context["csp_nonce"]
self.assertIn(f'<script nonce="{nonce}">', response.text)
def test_csp_nonce_length(self):
response = self.client.get("/csp_nonce/")
nonce = response.context["csp_nonce"]
self.assertEqual(len(nonce), 22) # Based on secrets.token_urlsafe of 16 bytes.

View File

@ -5,4 +5,5 @@ from . import views
urlpatterns = [ urlpatterns = [
path("request_attrs/", views.request_processor), path("request_attrs/", views.request_processor),
path("debug/", views.debug_processor), path("debug/", views.debug_processor),
path("csp_nonce/", views.csp_nonce_processor),
] ]

View File

@ -13,3 +13,7 @@ def debug_processor(request):
"other_debug_objects": DebugObject.objects.using("other"), "other_debug_objects": DebugObject.objects.using("other"),
} }
return render(request, "context_processors/debug.html", context) return render(request, "context_processors/debug.html", context)
def csp_nonce_processor(request):
return render(request, "context_processors/csp_nonce.html")

View File

@ -0,0 +1,135 @@
import time
from utils_tests.test_csp import basic_config, basic_policy
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import SimpleTestCase
from django.test.selenium import SeleniumTestCase
from django.test.utils import modify_settings, override_settings
from django.utils.csp import CSP
from .views import csp_reports
@override_settings(
MIDDLEWARE=["django.middleware.csp.ContentSecurityPolicyMiddleware"],
ROOT_URLCONF="middleware.urls",
)
class CSPMiddlewareTest(SimpleTestCase):
@override_settings(SECURE_CSP=None, SECURE_CSP_REPORT_ONLY=None)
def test_csp_defaults_off(self):
response = self.client.get("/csp-base/")
self.assertNotIn(CSP.HEADER_ENFORCE, response)
self.assertNotIn(CSP.HEADER_REPORT_ONLY, response)
@override_settings(SECURE_CSP=basic_config, SECURE_CSP_REPORT_ONLY=None)
def test_csp_basic(self):
"""
With SECURE_CSP set to a valid value, the middleware adds a
"Content-Security-Policy" header to the response.
"""
response = self.client.get("/csp-base/")
self.assertEqual(response[CSP.HEADER_ENFORCE], basic_policy)
self.assertNotIn(CSP.HEADER_REPORT_ONLY, response)
@override_settings(SECURE_CSP={"default-src": [CSP.SELF, CSP.NONCE]})
def test_csp_basic_with_nonce(self):
"""
Test the nonce is added to the header and matches what is in the view.
"""
response = self.client.get("/csp-nonce/")
nonce = response.text
self.assertTrue(nonce)
self.assertEqual(
response[CSP.HEADER_ENFORCE], f"default-src 'self' 'nonce-{nonce}'"
)
@override_settings(SECURE_CSP={"default-src": [CSP.SELF, CSP.NONCE]})
def test_csp_basic_with_nonce_but_unused(self):
"""
Test if `request.csp_nonce` is never accessed, it is not added to the header.
"""
response = self.client.get("/csp-base/")
nonce = response.text
self.assertIsNotNone(nonce)
self.assertEqual(response[CSP.HEADER_ENFORCE], basic_policy)
@override_settings(SECURE_CSP=None, SECURE_CSP_REPORT_ONLY=basic_config)
def test_csp_report_only_basic(self):
"""
With SECURE_CSP_REPORT_ONLY set to a valid value, the middleware adds a
"Content-Security-Policy-Report-Only" header to the response.
"""
response = self.client.get("/csp-base/")
self.assertEqual(response[CSP.HEADER_REPORT_ONLY], basic_policy)
self.assertNotIn(CSP.HEADER_ENFORCE, response)
@override_settings(
SECURE_CSP=basic_config,
SECURE_CSP_REPORT_ONLY=basic_config,
)
def test_csp_both(self):
"""
If both SECURE_CSP and SECURE_CSP_REPORT_ONLY are set, the middleware
adds both headers to the response.
"""
response = self.client.get("/csp-base/")
self.assertEqual(response[CSP.HEADER_ENFORCE], basic_policy)
self.assertEqual(response[CSP.HEADER_REPORT_ONLY], basic_policy)
@override_settings(
DEBUG=True,
SECURE_CSP=basic_config,
SECURE_CSP_REPORT_ONLY=basic_config,
)
def test_csp_404_debug_view(self):
"""
Test that the CSP headers are not added to the debug view.
"""
response = self.client.get("/csp-404/")
self.assertNotIn(CSP.HEADER_ENFORCE, response)
self.assertNotIn(CSP.HEADER_REPORT_ONLY, response)
@override_settings(
DEBUG=True,
SECURE_CSP=basic_config,
SECURE_CSP_REPORT_ONLY=basic_config,
)
def test_csp_500_debug_view(self):
"""
Test that the CSP headers are not added to the debug view.
"""
response = self.client.get("/csp-500/")
self.assertNotIn(CSP.HEADER_ENFORCE, response)
self.assertNotIn(CSP.HEADER_REPORT_ONLY, response)
@override_settings(
ROOT_URLCONF="middleware.urls",
SECURE_CSP_REPORT_ONLY={
"default-src": [CSP.NONE],
"img-src": [CSP.SELF],
"script-src": [CSP.SELF],
"style-src": [CSP.SELF],
"report-uri": "/csp-report/",
},
)
@modify_settings(
MIDDLEWARE={"append": "django.middleware.csp.ContentSecurityPolicyMiddleware"}
)
class CSPSeleniumTestCase(SeleniumTestCase, StaticLiveServerTestCase):
available_apps = ["middleware"]
def setUp(self):
self.addCleanup(csp_reports.clear)
super().setUp()
def test_reports_are_generated(self):
url = self.live_server_url + "/csp-failure/"
self.selenium.get(url)
time.sleep(1) # Allow time for the CSP report to be sent.
reports = sorted(
(r["csp-report"]["document-uri"], r["csp-report"]["violated-directive"])
for r in csp_reports
)
self.assertEqual(reports, [(url, "img-src"), (url, "style-src-elem")])

View File

@ -1,4 +1,5 @@
from django.urls import path, re_path from django.urls import path, re_path
from django.views.debug import default_urlconf
from . import views from . import views
@ -11,4 +12,10 @@ urlpatterns = [
# Should not append slash. # Should not append slash.
path("sensitive_fbv/", views.sensitive_fbv), path("sensitive_fbv/", views.sensitive_fbv),
path("sensitive_cbv/", views.SensitiveCBV.as_view()), path("sensitive_cbv/", views.SensitiveCBV.as_view()),
# Used in CSP tests.
path("csp-failure/", default_urlconf),
path("csp-report/", views.csp_report_view),
path("csp-base/", views.empty_view),
path("csp-nonce/", views.csp_nonce),
path("csp-500/", views.csp_500),
] ]

View File

@ -1,6 +1,12 @@
import json
import sys
from django.http import HttpResponse from django.http import HttpResponse
from django.middleware.csp import get_nonce
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.debug import technical_500_response
from django.views.decorators.common import no_append_slash from django.views.decorators.common import no_append_slash
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View from django.views.generic import View
@ -17,3 +23,25 @@ def sensitive_fbv(request, *args, **kwargs):
class SensitiveCBV(View): class SensitiveCBV(View):
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
return HttpResponse() return HttpResponse()
def csp_nonce(request):
return HttpResponse(get_nonce(request))
def csp_500(request):
try:
raise Exception
except Exception:
return technical_500_response(request, *sys.exc_info())
csp_reports = []
@csrf_exempt
def csp_report_view(request):
if request.method == "POST":
data = json.loads(request.body)
csp_reports.append(data)
return HttpResponse(status=204)

View File

@ -0,0 +1,166 @@
from secrets import token_urlsafe
from unittest.mock import patch
from django.test import SimpleTestCase
from django.utils.csp import CSP, LazyNonce, build_policy
from django.utils.functional import empty
basic_config = {
"default-src": [CSP.SELF],
}
alt_config = {
"default-src": [CSP.SELF, CSP.UNSAFE_INLINE],
}
basic_policy = "default-src 'self'"
class CSPConstantsTests(SimpleTestCase):
def test_constants(self):
self.assertEqual(CSP.NONE, "'none'")
self.assertEqual(CSP.REPORT_SAMPLE, "'report-sample'")
self.assertEqual(CSP.SELF, "'self'")
self.assertEqual(CSP.STRICT_DYNAMIC, "'strict-dynamic'")
self.assertEqual(CSP.UNSAFE_EVAL, "'unsafe-eval'")
self.assertEqual(CSP.UNSAFE_HASHES, "'unsafe-hashes'")
self.assertEqual(CSP.UNSAFE_INLINE, "'unsafe-inline'")
self.assertEqual(CSP.WASM_UNSAFE_EVAL, "'wasm-unsafe-eval'")
self.assertEqual(CSP.NONCE, "<CSP_NONCE_SENTINEL>")
class CSPBuildPolicyTest(SimpleTestCase):
def assertPolicyEqual(self, a, b):
parts_a = sorted(a.split("; ")) if a is not None else None
parts_b = sorted(b.split("; ")) if b is not None else None
self.assertEqual(parts_a, parts_b, f"Policies not equal: {a!r} != {b!r}")
def test_config_empty(self):
self.assertPolicyEqual(build_policy({}), "")
def test_config_basic(self):
self.assertPolicyEqual(build_policy(basic_config), basic_policy)
def test_config_multiple_directives(self):
policy = {
"default-src": [CSP.SELF],
"script-src": [CSP.NONE],
}
self.assertPolicyEqual(
build_policy(policy), "default-src 'self'; script-src 'none'"
)
def test_config_value_as_string(self):
"""
Test that a single value can be passed as a string.
"""
policy = {"default-src": CSP.SELF}
self.assertPolicyEqual(build_policy(policy), "default-src 'self'")
def test_config_value_as_tuple(self):
"""
Test that a tuple can be passed as a value.
"""
policy = {"default-src": (CSP.SELF, "foo.com")}
self.assertPolicyEqual(build_policy(policy), "default-src 'self' foo.com")
def test_config_value_as_set(self):
"""
Test that a set can be passed as a value.
Sets are often used in Django settings to ensure uniqueness, however, sets are
unordered. The middleware ensures consistency via sorting if a set is passed.
"""
policy = {"default-src": {CSP.SELF, "foo.com", "bar.com"}}
self.assertPolicyEqual(
build_policy(policy), "default-src 'self' bar.com foo.com"
)
def test_config_value_none(self):
"""
Test that `None` removes the directive from the policy.
Useful in cases where the CSP config is scripted in some way or
explicitly not wanting to set a directive.
"""
policy = {"default-src": [CSP.SELF], "script-src": None}
self.assertPolicyEqual(build_policy(policy), basic_policy)
def test_config_value_boolean_true(self):
policy = {"default-src": [CSP.SELF], "block-all-mixed-content": True}
self.assertPolicyEqual(
build_policy(policy), "default-src 'self'; block-all-mixed-content"
)
def test_config_value_boolean_false(self):
policy = {"default-src": [CSP.SELF], "block-all-mixed-content": False}
self.assertPolicyEqual(build_policy(policy), basic_policy)
def test_config_value_multiple_boolean(self):
policy = {
"default-src": [CSP.SELF],
"block-all-mixed-content": True,
"upgrade-insecure-requests": True,
}
self.assertPolicyEqual(
build_policy(policy),
"default-src 'self'; block-all-mixed-content; upgrade-insecure-requests",
)
def test_config_with_nonce_arg(self):
"""
Test when the `CSP.NONCE` is not in the defined policy, the nonce
argument has no effect.
"""
self.assertPolicyEqual(build_policy(basic_config, nonce="abc123"), basic_policy)
def test_config_with_nonce(self):
policy = {"default-src": [CSP.SELF, CSP.NONCE]}
self.assertPolicyEqual(
build_policy(policy, nonce="abc123"),
"default-src 'self' 'nonce-abc123'",
)
def test_config_with_multiple_nonces(self):
policy = {
"default-src": [CSP.SELF, CSP.NONCE],
"script-src": [CSP.SELF, CSP.NONCE],
}
self.assertPolicyEqual(
build_policy(policy, nonce="abc123"),
"default-src 'self' 'nonce-abc123'; script-src 'self' 'nonce-abc123'",
)
def test_config_with_empty_directive(self):
policy = {"default-src": []}
self.assertPolicyEqual(build_policy(policy), "")
class LazyNonceTests(SimpleTestCase):
def test_generates_on_usage(self):
generated_tokens = []
nonce = LazyNonce()
self.assertFalse(nonce)
self.assertIs(nonce._wrapped, empty)
def memento_token_urlsafe(size):
generated_tokens.append(result := token_urlsafe(size))
return result
with patch("django.utils.csp.secrets.token_urlsafe", memento_token_urlsafe):
# Force usage, similar to template rendering, to generate the nonce.
val = str(nonce)
self.assertTrue(nonce)
self.assertEqual(nonce, val)
self.assertIsInstance(nonce, str)
self.assertEqual(len(val), 22) # Based on secrets.token_urlsafe of 16 bytes.
self.assertEqual(generated_tokens, [nonce])
# Also test the wrapped value.
self.assertEqual(nonce._wrapped, val)
def test_returns_same_value(self):
nonce = LazyNonce()
first = str(nonce)
second = str(nonce)
self.assertEqual(first, second)