Configuring CSP: A Test For Django 6.0



This content originally appeared on DEV Community and was authored by Chiemezuo

Right away, some things could potentially be confusing about the title of this blog post. What is CSP? How does it relate to Django? Why Django 6.0?
You would be fair to ask these questions, especially because at the time of writing this, the current version of Django is 5.2.5. Let’s get these questions answered.

What is CSP?

question mark GIF

CSP is an acronym that stands for “Content Security Policy“. Just as the name implies, it has a lot to do with “security“, and this is achieved by placing restrictions (or policies) on the content from websites. Over the years, browsers have evolved to be incredibly powerful, while making sure that content accessed on one website cannot run malicious instructions in the context of another website tab or another part of the computer altogether. However, this leaves a small window of opportunity for the said malicious instructions: “the context of the same website itself”. That might sound vague, but if malicious code could somehow get served from whatever website you’re on, then it can be executed in the context you’re in. An example of this would be if there were a console.log statement in the resulting HTML of a website, an inspection of the dev tools console would reveal it. Here’s where things get interesting:

  1. Something far more harmful than console.log scripts could be done outside your immediate view.
  2. On some websites, especially content-heavy ones without data sanitization, these scripts could just about be added by anybody.
  3. The scripts don’t even have to appear to be on your website (as in the case of “clickjacking“).

How does this relate to Django?

Django is one of the most used web frameworks on the planet, and can be a conduit for the types of attacks that CSP helps prevent: clickjacking and cross-site scripting (XSS). CSP also helps with making sure Django loads your site’s pages in HTTPS.
In summary, CSP is tool-agnostic; Django developers would benefit from a good grasp of it.

Why Django 6.0?

There has been existing tooling to test and enforce CSP in Django. The most recognizable of those has been the django-csp package developed by a team at Mozilla. It is available on PyPI and does an excellent job.
You might still be wondering how this answers the question: “Why Django 6.0?” In May 2024, a conversation began to explore the possibility of adding CSP support to Django. The idea was to create something better, with no need for another package installation, and with good out-of-the-box support. That conversation morphed into a Pull Request that has since been merged and will be coming with the release of Django 6.0, scheduled for December 2025 at the time of writing.
Thankfully, you can get to testing it right away, and that is what the rest of this blog post will show you how to do.

Setting up Django 6.0

animated duck building bricks
To fully understand the setups I used, an understanding of my motivation for this Django 6.0 test run would be helpful. All of this CSP work was geared towards my Google Summer of Code (GSoC) 2025 project with Wagtail. The project was about fixing the gaps and ensuring strict CSP support for Wagtail.
In a sense, everything I did revolved around the Wagtail CMS, and how sites built with it interfaced with Django.

I tested with two different open-source Wagtail websites:

  1. The BakeryDemo
  2. The official marketing website of Wagtail

The Bakery Demo project uses pip from Python for package management, and the Wagtail dot org website uses Poetry. The differences in connecting both were very subtle, with the bakery demo being the easier of the two. The overarching requirement was that you would have cloned the most recent version of Django from its GitHub repository.
For the Bakery Demo, you would need a virtual environment and an installation of Django pointing to your local editable version via pip install -e /path/to/django. This virtual environment would contain your Django, editable version of Wagtail, and Bakery Demo website.
For a poetry-based installation, modifying the project’s pyproject.toml to point to a locally editable version of Django (or mounted volume of it, as in a potential case of containerization)

Note: Keep in mind that you may need to upgrade some dependencies depending on the support hitherto. This may not be the case for most websites you may want to test this with.

Testing a Wagtail/Django site with CSP.

At the time of writing, the docs for using the built-in CSP tool proved very useful. You could choose to set up CSP to outrightly block unsupported content using the Content-Security-Policy header in your settings, or you could set it up to report CSP violations (either on the console or via a dedicated URL) with Content-Security-Policy-Report-Only.
The question of how to write the logic is something that comes with a bit of flexibility. Ultimately, the built-in CSP support in Django reads from your settings.py file (which can sometimes be split into base.py, dev.py, and prod.py. A setting like the following would get you on your way to Django’s CSP usage:

from django.utils.csp import CSP

MIDDLEWARE.append("django.middleware.csp.ContentSecurityPolicyMiddleware")

# To enforce a CSP policy in report-only mode:
SECURE_CSP_REPORT_ONLY = {
    "default-src": [CSP.SELF],
    # Add more directives as needed.
}

If you are already familiar with the external django-csp package, you may have noticed some slight differences in syntax.

As shown in the comment of the snippet, you can add more CSP directives as you please. This is where the previously mentioned flexibility comes in. You can either write the CSP directives directly in the settings file, or have some logic still in the settings to read from an env.

At the project settings level, you can ensure that certain directives cannot be removed or added without changing the state of the codebase, but that can prove rigid if changes are often required. At the environment level (.env), you can frequently change things, but risk breaking things if the patterns or string literals do not match what is expected. And a combination of both environment and project levels can give you the best of both worlds, only if you make sure that you don’t unintentionally make overrides to some directives you otherwise would have liked to leave.

I tested the Wagtail dot org and Bakery Demo sites, and all the cases work as expected, with no visible difference in performance.

Thoughts

an animated illustration showing thoughts in an avatar's brain
I think this is a huge step for Django for a lot of reasons.

  • Django aims to follow Python’s “batteries included” philosophy, and already includes a lot of security features. I think CSP would make a great addition to that list.
  • A lot of users may not have already been aware of the concept of CSP, but even the idea of them seeing it in a changelog or “What’s new?” section would easily bring this to their notice.
  • The external Django-csp tool is a powerful tool, but I imagine the community feedback on an equivalent Django internal would lead to better iterations.
  • I also like the idea of CSP decorators that will let you fine-tune the CSP configurations for each view!

Conclusions

I’m looking forward to seeing this feature in Django 6.0, and I’ll definitely be on the train of people updating functionality on Django sites that I contribute to. I feel adding this to the already-included “Django batteries” is the right direction.

Special credits to Rob Hudson for taking charge of this feature.

Thanks for reading!


This content originally appeared on DEV Community and was authored by Chiemezuo