[Django] #33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)

5 views
Skip to first unread message

Django

unread,
Oct 8, 2021, 12:23:57 PM10/8/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
-------------------------------------------+------------------------
Reporter: Adam Johnson | Owner: (none)
Type: Bug | Status: new
Component: Error reporting | Version: dev
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------------+------------------------
When using a strict CSP header, the debug view is broken as the inline CSS
and JS get blocked by the browser. The page is somewhat usable as all the
text is all visible, but it isn't particularly friendly or necessarily
obvious why it's broken.

For example:

I thought of a couple of potential fixes:

1. Make the debug http response a special class that deletes any CSP
header added to it. This would work for most cases but not if the CSP
header is added outside of Django (WSGI middleware, a server wrapping
runserver).
2. Move the CSS and JS to separate URL's so they're compatible with CSP.
They could be served by a special route/path in `runserver`.

I am personally leaning towards #2 - even though it's more complex, it is
more general.

There's also some opportunity to modernize the CSS and JS in the debug
page a bit, such as rewriting use of window.onload and onclick handlers.

To reproduce, you can save the below as `app.py` and run `python app.py
runserver`:

{{{
import sys

from django.conf import settings
from django.urls import path

settings.configure(
DEBUG=True,
ROOT_URLCONF=__name__,
SECRET_KEY="django-insecure-
r42jn$xf4g+=w@=l#m6ghqo0!$icww-h4+$5gojq(1ld$x%!6f",
MIDDLEWARE=[f"{__name__}.CSPMiddleware"],
)


class CSPMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
response = self.get_response(request)
response[
"Content-Security-Policy"
] = "object-src 'none'; base-uri 'none'; default-src 'self';"
return response


def index(request):
1 / 0


urlpatterns = [path("", index)]

if __name__ == "__main__":
from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/33180>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Oct 8, 2021, 12:24:54 PM10/8/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
---------------------------------+--------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Bug | Status: new
Component: Error reporting | Version: dev
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+--------------------------------------
Changes (by Adam Johnson):

* Attachment "error.png" added.

Django

unread,
Oct 8, 2021, 12:25:34 PM10/8/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
---------------------------------+--------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Bug | Status: new
Component: Error reporting | Version: dev
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+--------------------------------------
Description changed by Adam Johnson:

Old description:

New description:

When using a strict CSP header, the debug view is broken as the inline CSS
and JS get blocked by the browser. The page is somewhat usable as all the
text is all visible, but it isn't particularly friendly or necessarily
obvious why it's broken.

For example:

[[Image(https://code.djangoproject.com/attachment/ticket/33180/error.png)]]

{{{
import sys

execute_from_command_line(sys.argv)
}}}

--

--
Ticket URL: <https://code.djangoproject.com/ticket/33180#comment:1>

Django

unread,
Oct 8, 2021, 12:26:17 PM10/8/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
---------------------------------+--------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Bug | Status: new
Component: Error reporting | Version: dev
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+--------------------------------------
Description changed by Adam Johnson:

Old description:

> When using a strict CSP header, the debug view is broken as the inline


> CSS and JS get blocked by the browser. The page is somewhat usable as all
> the text is all visible, but it isn't particularly friendly or
> necessarily obvious why it's broken.
>
> For example:
>

> [[Image(https://code.djangoproject.com/attachment/ticket/33180/error.png)]]

New description:

When using a strict CSP header, the debug view is broken as the inline CSS
and JS get blocked by the browser. The page is somewhat usable as all the
text is all visible, but it isn't particularly friendly or necessarily
obvious why it's broken.

For example:

[[Image(ticket:33180:error.png)]]

{{{
import sys

execute_from_command_line(sys.argv)
}}}

--

--
Ticket URL: <https://code.djangoproject.com/ticket/33180#comment:2>

Django

unread,
Oct 8, 2021, 12:27:29 PM10/8/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
---------------------------------+--------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Bug | Status: new
Component: Error reporting | Version: dev
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+--------------------------------------
Changes (by Adam Johnson):

* Attachment "error.png" added.


Django

unread,
Oct 8, 2021, 12:27:29 PM10/8/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
---------------------------------+--------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Bug | Status: new
Component: Error reporting | Version: dev
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+--------------------------------------
Changes (by Adam Johnson):

* Attachment "error.png" removed.

Django

unread,
Oct 9, 2021, 7:15:14 AM10/9/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
--------------------------------------+------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Cleanup/optimization | Status: new

Component: Error reporting | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted

Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------------+------------------------------------
Changes (by Mariusz Felisiak):

* type: Bug => Cleanup/optimization
* stage: Unreviewed => Accepted


Comment:

Personally, I prefer the second option.

Follow up to #32624.

--
Ticket URL: <https://code.djangoproject.com/ticket/33180#comment:3>

Django

unread,
Oct 11, 2021, 5:53:16 PM10/11/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
--------------------------------------+------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Cleanup/optimization | Status: new

Component: Error reporting | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------------+------------------------------------

Comment (by Adam Johnson):

One complication is that the HTML email reporter may be used to render
emails. Emails can't use separate CSS. I guess an implementation for #2
may need to pursue a separate CSS file that can be inlined when is_email =
True. (The JS is only used for browser renders.)

--
Ticket URL: <https://code.djangoproject.com/ticket/33180#comment:4>

Django

unread,
Oct 23, 2021, 1:56:18 PM10/23/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
--------------------------------------+------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Cleanup/optimization | Status: new

Component: Error reporting | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------------+------------------------------------

Comment (by Tom Carrick):

I'm not too sure about a special route for runserver. I'm probably not the
only person that sometimes uses gunicorn/etc. locally or in some other
locked down environment where I still want to see the error pages. I'm not
sure I have a good alternative though.

--
Ticket URL: <https://code.djangoproject.com/ticket/33180#comment:5>

Django

unread,
Oct 23, 2021, 4:45:22 PM10/23/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
--------------------------------------+------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Cleanup/optimization | Status: new

Component: Error reporting | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------------+------------------------------------

Comment (by Adam Johnson):

It needn't be done by modifying the server per se. It can probably be part
of the WSGI application, which should work under gunicorn etc.

--
Ticket URL: <https://code.djangoproject.com/ticket/33180#comment:6>

Django

unread,
Dec 4, 2021, 7:44:19 PM12/4/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
--------------------------------------+------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Cleanup/optimization | Status: new

Component: Error reporting | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------------+------------------------------------

Comment (by Teddy Ni):

For convenience, could this be implemented by using [https://content-
security-policy.com/nonce/ a nonce]?

--
Ticket URL: <https://code.djangoproject.com/ticket/33180#comment:7>

Django

unread,
Dec 5, 2021, 7:13:04 AM12/5/21
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
--------------------------------------+------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Cleanup/optimization | Status: new

Component: Error reporting | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------------+------------------------------------

Comment (by Adam Johnson):

We could use a nonce with a modified version of my suggested fix #1, since
any CSP header will be added *after* the response has been created. We'd
have to detect the header being added and pull the nonce out of it (if
it's there) or add it, both paths requiring us to parse the header. Sounds
complicated to me.

--
Ticket URL: <https://code.djangoproject.com/ticket/33180#comment:8>

Django

unread,
Mar 10, 2022, 4:56:49 PM3/10/22
to django-...@googlegroups.com
#33180: Debug 500 HTML broken with strict Content-Security-Policy (CSP)
--------------------------------------+------------------------------------

Reporter: Adam Johnson | Owner: (none)
Type: Cleanup/optimization | Status: new

Component: Error reporting | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------------+------------------------------------

Comment (by Collin Anderson):

I feel like ideally we get `Content-Security-Policy` framework added to
core (#15727) so there's a reliable way to edit CSP headers on a per-
response basis.

Re moving js/css to separate file/url, Google's CSP Evaluator https://csp-
evaluator.withgoogle.com/ (linked from
https://infosec.mozilla.org/guidelines/web_security#content-security-
policy) says regarding `script-src: self`:
"Host whitelists can frequently be bypassed. Consider using 'strict-
dynamic' in combination with CSP nonces or hashes." "'self' can be
problematic if you host JSONP, Angular or user uploaded files."

(There's probably pros and cons either way, as you could still have
injection in an inline-script in a template. `script-src: self` is pretty
popular, and `contrib.admin` relies on it being there.)

--
Ticket URL: <https://code.djangoproject.com/ticket/33180#comment:9>

Reply all
Reply to author
Forward
0 new messages