diff --git a/django/views/debug.py b/django/views/debug.py index ea06224bf6..30a1dbc6da 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -110,7 +110,7 @@ class SafeExceptionReporterFilter: cleansed_substitute = "********************" hidden_settings = _lazy_re_compile( - "API|TOKEN|KEY|SECRET|PASS|SIGNATURE", flags=re.I + "API|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE", flags=re.I ) def cleanse_setting(self, key, value): @@ -118,10 +118,13 @@ class SafeExceptionReporterFilter: Cleanse an individual setting key/value of sensitive content. If the value is a dictionary, recursively cleanse the keys in that dictionary. """ - try: - is_sensitive = self.hidden_settings.search(key) - except TypeError: - is_sensitive = False + if key == settings.SESSION_COOKIE_NAME: + is_sensitive = True + else: + try: + is_sensitive = self.hidden_settings.search(key) + except TypeError: + is_sensitive = False if is_sensitive: cleansed = self.cleansed_substitute @@ -158,6 +161,14 @@ class SafeExceptionReporterFilter: return {} return {k: self.cleanse_setting(k, v) for k, v in request.META.items()} + def get_safe_cookies(self, request): + """ + Return a dictionary of request.COOKIES with sensitive values redacted. + """ + if not hasattr(request, "COOKIES"): + return {} + return {k: self.cleanse_setting(k, v) for k, v in request.COOKIES.items()} + def is_active(self, request): """ This filter is to add safety in production environments (i.e. DEBUG @@ -359,6 +370,7 @@ class ExceptionReporter: "frames": frames, "request": self.request, "request_meta": self.filter.get_safe_request_meta(self.request), + "request_COOKIES_items": self.filter.get_safe_cookies(self.request).items(), "user_str": user_str, "filtered_POST_items": list( self.filter.get_post_parameters(self.request).items() @@ -376,7 +388,6 @@ class ExceptionReporter: if self.request is not None: c["request_GET_items"] = self.request.GET.items() c["request_FILES_items"] = self.request.FILES.items() - c["request_COOKIES_items"] = self.request.COOKIES.items() c["request_insecure_uri"] = self._get_raw_insecure_uri() c["raising_view_name"] = get_caller(self.request) diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index 6cd69d5857..b808a0a981 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -281,7 +281,11 @@ following attributes and methods: import re - re.compile(r'API|TOKEN|KEY|SECRET|PASS|SIGNATURE', flags=re.IGNORECASE) + re.compile(r'API|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE', flags=re.IGNORECASE) + + .. versionchanged:: 4.2 + + ``HTTP_COOKIE`` was added. .. method:: is_active(request) diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt index 9c6526a3a0..58e4bd12ac 100644 --- a/docs/releases/4.2.txt +++ b/docs/releases/4.2.txt @@ -176,6 +176,9 @@ Forms * :func:`~django.forms.models.modelform_factory` now respects the ``formfield_callback`` attribute of the ``form``’s ``Meta``. +* Session cookies are now treated as credentials and therefore hidden and + replaced with stars (``**********``) in error reports. + Generic Views ~~~~~~~~~~~~~ diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py index 5a07131349..7224a2b6b6 100644 --- a/tests/view_tests/tests/test_debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -1696,6 +1696,12 @@ class ExceptionReporterFilterTests( ) self.assertNotIn(b"super_secret", response.content) + @override_settings(SESSION_COOKIE_NAME="djangosession") + def test_cleanse_session_cookie_value(self): + self.client.cookies.load({"djangosession": "should not be displayed"}) + response = self.client.get("/raises500/") + self.assertNotContains(response, "should not be displayed", status_code=500) + class CustomExceptionReporterFilter(SafeExceptionReporterFilter): cleansed_substitute = "XXXXXXXXXXXXXXXXXXXX"