django-pipeline-csp
CSP nonce support for django-pipeline script and stylesheet tags.
Under a nonce-based Content Security Policy with 'strict-dynamic', browsers ignore host allowlists, so every
<script> needs the request's nonce. django-pipeline renders its tags without one
(jazzband/django-pipeline#771). django-pipeline-csp provides a
drop-in {% javascript %} tag that adds it.
Installation
pip install django-pipeline-csp
INSTALLED_APPS = [
# ...
"pipeline",
"pipeline_csp",
]
Usage
Replace {% load pipeline %} with {% load pipeline_csp %}:
{% load pipeline_csp %}
{% stylesheet "base" %}
{% javascript "polyfills" %}
Every <script> rendered by {% javascript %} gets nonce="..." — individual source files
(PIPELINE_ENABLED = False), the compressed bundle and inline JavaScript templates alike. Likewise every <link>
rendered by {% stylesheet %}.
Do not load both libraries in one template: the library loaded last wins, and with
{% load pipeline_csp pipeline %} the nonce is silently missing.
Form and widget media (class Media)
Django renders {{ form.media }} itself, so those <link> and <script> tags have no nonce either — including
PipelineFormMedia with css_packages / js_packages. There are two ways to add it.
Everywhere: MediaNonceMiddleware
Add the middleware after your CSP middleware:
MIDDLEWARE = [
# ...
"csp.middleware.CSPMiddleware", # or django.middleware.csp.ContentSecurityPolicyMiddleware
"pipeline_csp.middleware.MediaNonceMiddleware",
]
Every <link> and <script> rendered by a Media object during the request gets the nonce: your templates, templates of
third-party apps and the Django admin alike, without changing any template. When the middleware is loaded it wraps
django.forms.Media.render_css and render_js once; outside a request (or without a nonce) the output is unchanged.
Only tags generated by Media from Python code are touched, never the response as a whole, so injected HTML never
receives the nonce.
Per template: csp_nonce filter
{% load pipeline_csp %}
{{ form.media|csp_nonce:request }}
{{ form.media.js|csp_nonce:request }}
Renders like {{ form.media }}, with the nonce on every <link> and <script>. Values that are not Media, request=None or
a missing nonce leave the output unchanged. request must exist in the context: Django resolves filter arguments
strictly, so a missing variable raises VariableDoesNotExist. Keep this in mind for templates that are rendered
without a request, e.g. by Django's default server_error view for 500.html.
Filter and middleware can be combined; the nonce is never added twice.
Nonce sources
The nonce is taken from, in this order:
- Django's built-in CSP (Django >= 6.0):
django.middleware.csp.ContentSecurityPolicyMiddleware - django-csp >= 4.0:
csp.middleware.CSPMiddleware(pip install django-pipeline-csp[django-csp])
Your policy must include the nonce in script-src, e.g. CSP.NONCE (Django) or csp.constants.NONCE (django-csp).
For stylesheets, add it to style-src as well. Note that browsers ignore 'unsafe-inline' in style-src once a
nonce is present there; inline style="..." attributes then need style-src-attr 'unsafe-inline' (or no inline
styles at all). Host sources in style-src keep working alongside the nonce.
Without an active middleware the output is identical to django-pipeline's.
request must be in the template context (django.template.context_processors.request).
The tag must render before the CSP middleware writes the response header — normal template rendering already
satisfies this. Accessing the nonce after the header was written raises CSPNonceError with django-csp; with
Django's built-in CSP a late nonce is simply not included in the header and the scripts it was meant to allow are
blocked. This matters mainly for streaming responses, where content can be produced after the headers are sent.
Not covered
- Jinja2 templates
- Inline
<style>blocks in your own templates: usenonce="{{ CSP_NONCE }}"(django-csp context processorcsp.context_processors.nonce) ornonce="{{ csp_nonce }}"(Django >= 6.0,django.template.context_processors.csp) - Scripts injected via
document.write('strict-dynamic'does not trust parser-inserted scripts) - Inline event handlers such as
onclick="..."
Compatibility
Python 3.10–3.14, Django 4.2 / 5.0 / 5.1 / 5.2 / 6.0 / 6.1, django-pipeline >= 4.1.
License
MIT
Release files for django-pipeline-csp 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_pipeline_csp-0.3.0.tar.gz | 12.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_pipeline_csp-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.8 kB
Release files / django_pipeline_csp-0.3.0.tar.gz
| Download URL | django_pipeline_csp-0.3.0.tar.gz |
|---|---|
| Size | 12.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4236a4163682c8d005344a50f88776b61a763513918a21b3526831d415762bd9
|
|
BLAKE2b-256 checksum How to use checksums |
72aa30efb8c860c1a181bbaaec7a8766e59d1a4bfd4221a8856da432e5e186cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / django_pipeline_csp-0.3.0-py3-none-any.whl
| Download URL | django_pipeline_csp-0.3.0-py3-none-any.whl |
|---|---|
| Size | 9.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1f2a5a9eb2dc60559b16f807837f85c3563ca87f61a94229b25467b0c1de7f22
|
|
BLAKE2b-256 checksum How to use checksums |
05ea29c10039505e6886781cadc20da1f636d698e8a6cc28888cc06a7e1209af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log