How to use a Content Security Policy

A Content Security Policy (CSP) tells the browser which scripts and stylesheets it is allowed to run. The strictest practical policy is a nonce-based one: the server generates a random token for every response, puts it in the Content-Security-Policy header, and repeats it on every asset it trusts. Anything without the token – including markup injected by an attacker – is refused.

django CMS renders the nonce on all assets it emits itself, so the admin, the page tree, the toolbar and the wizards keep working under such a policy.

Added in version 5.1: Nonce support requires Django 6.1 or later. On Django 5.2 and 6.0 the templates render exactly as before, without a nonce attribute.

Set-up

Django 6.1 ships CSP support out of the box. Three pieces are needed:

  1. Add the middleware that generates the nonce and writes the header:

    MIDDLEWARE = [
        "django.middleware.csp.ContentSecurityPolicyMiddleware",
        ...
    ]
    
  2. Add the context processor that makes the nonce available to templates:

    TEMPLATES = [
        {
            ...
            "OPTIONS": {
                "context_processors": [
                    ...
                    "django.template.context_processors.csp",
                ],
            },
        },
    ]
    
  3. Declare a policy that uses the nonce placeholder:

    from django.utils.csp import CSP
    
    SECURE_CSP = {
        "default-src": [CSP.SELF],
        "script-src": [CSP.SELF, CSP.NONCE],
        "style-src": [CSP.SELF, CSP.NONCE],
    }
    

CSP.NONCE is replaced with the request’s nonce when the header is built. See Django’s CSP how-to for the full picture.

Note

Start with SECURE_CSP_REPORT_ONLY instead of SECURE_CSP. The browser then reports violations without blocking anything, which lets you find assets from your own templates or from third-party apps that do not carry the nonce yet.

Nonces in your own templates

Any script or stylesheet you add has to carry the nonce as well. Django 6.1 registers a built-in {% csp_nonce_attr %} tag for that, but it does not exist on Django 5.2 and 6.0. If your project or add-on supports those versions too, load django CMS’ cross-version version of the tag instead:

{% load cms_static %}

<script src="{% static 'myapp/js/app.js' %}" {% csp_nonce_attr %}></script>
<link rel="stylesheet" href="{% static 'myapp/css/app.css' %}" {% csp_nonce_attr %}>
<style {% csp_nonce_attr %}>...</style>

The tag renders nonce="..." when a nonce is available and nothing at all otherwise, so a single template works on every supported Django version.

Form and widget media take the nonce as an argument:

{% csp_nonce_attr form.media %}

If you need the assets one at a time – for example to wrap each of them in a separate sekizai {% addtoblock %}, the way the toolbar does – use {% render_media_assets %}:

{% render_media_assets form.media "css" as css_assets %}
{% for css in css_assets %}
    {% addtoblock "css" %}{{ css }}{% endaddtoblock %}
{% endfor %}

The second argument is either "css" or "js".

What does not need a nonce

<script type="application/json"> data blocks – such as the toolbar’s cms-config-json element and the JSON emitted by json_script – carry no nonce, and must not be given one. Browsers never execute them, so CSP never checks them; this matches Django’s own json_script().

<link rel="icon"> and other non-stylesheet links are governed by img-src rather than style-src and take no nonce either.

Caching and nonces

A nonce is valid for exactly one response. Never store a rendered page that contains one in a shared cache, or the browser will reject the assets of every later visitor.

django CMS’ own page cache is safe here: it only serves cached responses to anonymous visitors who see no toolbar, so no nonce-bearing markup ever enters it. Take the same care with any caching you add yourself, and be aware that Django’s UpdateCacheMiddleware does not know about nonces.

Inline JavaScript

django CMS itself contains no inline JavaScript and no inline event handlers, so a policy without 'unsafe-inline' needs no exceptions for the CMS. If your own templates still use onclick="..." attributes or inline <script> blocks, move them into static files – a nonce cannot rescue inline event handlers, which are only allowed by 'unsafe-inline' or 'unsafe-hashes'.