From 5e40463e56143081f9e2dc925576b1e6e12af857 Mon Sep 17 00:00:00 2001 From: Brian Helba Date: Tue, 10 Nov 2026 21:07:40 -0500 Subject: [PATCH] feat(django): Filter `sensitive_post_parameters` from request data Django's `sensitive_post_parameters` decorator marks POST parameters whose values should be kept out of error reports. Previously, the Django integration ignored it, so those values were sent to Sentry as part of the event's request data. Replace the values of the marked parameters with `[Filtered]`. This covers data in form bodies, Django REST Framework's `request.data`, and JSON object bodies. See: https://docs.djangoproject.com/en/stable/howto/error-reporting/#filtering-sensitive-information Related to #2356, but the `sensitive_variables` decorator is not handled by this. --- sentry_sdk/integrations/django/__init__.py | 19 ++++++++++-- tests/integrations/django/myapp/urls.py | 18 +++++++++++ tests/integrations/django/test_basic.py | 35 ++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index bff2982d35..b6b678e9ee 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -807,9 +807,24 @@ def size_of_file(self, file: "Any") -> int: def parsed_body(self) -> "Optional[Dict[str, Any]]": try: - return self.request.data + data = self.request.data except Exception: - return RequestExtractor.parsed_body(self) + data = RequestExtractor.parsed_body(self) + + # Set by Django's `sensitive_post_parameters` decorator + sensitive_post_parameters = getattr( + self.request, "sensitive_post_parameters", None + ) + if sensitive_post_parameters and isinstance(data, dict): + if sensitive_post_parameters == "__ALL__": + sensitive_post_parameters = data.keys() + + data = { + k: SENSITIVE_DATA_SUBSTITUTE if k in sensitive_post_parameters else v + for k, v in data.items() + } + + return data def _set_user_info(request: "WSGIRequest", event: "Event") -> None: diff --git a/tests/integrations/django/myapp/urls.py b/tests/integrations/django/myapp/urls.py index 7dda30d7ee..2bfb226e60 100644 --- a/tests/integrations/django/myapp/urls.py +++ b/tests/integrations/django/myapp/urls.py @@ -23,6 +23,7 @@ def path(path, *args, **kwargs): return url("^{}$".format(path), *args, **kwargs) +from django.views.decorators.debug import sensitive_post_parameters from django_helpers import views as helper_views from . import views @@ -60,6 +61,16 @@ def path(path, *args, **kwargs): name="sentryclass_csrf", ), path("post-echo", views.post_echo, name="post_echo"), + path( + "sensitive-post-parameters", + sensitive_post_parameters("credit_card_number")(views.message), + name="sensitive_post_parameters", + ), + path( + "sensitive-post-parameters-all", + sensitive_post_parameters()(views.message), + name="sensitive_post_parameters_all", + ), path("template-exc", views.template_exc, name="template_exc"), path("template-test", views.template_test, name="template_test"), path("template-test2", views.template_test2, name="template_test2"), @@ -154,6 +165,13 @@ def path(path, *args, **kwargs): name="rest_framework_read_body_and_exc", ) ) + urlpatterns.append( + path( + "rest-framework-sensitive-post-parameters", + sensitive_post_parameters("credit_card_number")(views.rest_framework_exc), + name="rest_framework_sensitive_post_parameters", + ) + ) urlpatterns.append( path( "rest-authenticated-hello", diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index 59afe9be6b..1026816baa 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -1389,6 +1389,41 @@ def raw_data(self): assert "data" not in event["request"] +@pytest.mark.parametrize( + "route, expected_data", + [ + ( + "sensitive_post_parameters", + {"name": "Jane", "credit_card_number": "[Filtered]"}, + ), + ( + "sensitive_post_parameters_all", + {"name": "[Filtered]", "credit_card_number": "[Filtered]"}, + ), + ( + "rest_framework_sensitive_post_parameters", + {"name": "Jane", "credit_card_number": "[Filtered]"}, + ), + ], +) +def test_sensitive_post_parameters( + sentry_init, client, capture_events, route, expected_data +): + if route == "rest_framework_sensitive_post_parameters": + pytest.importorskip("rest_framework") + + sentry_init(integrations=[DjangoIntegration()]) + events = capture_events() + + client.post( + reverse(route), + data={"name": "Jane", "credit_card_number": "4111111111111111"}, + ) + + (event,) = events + assert event["request"]["data"] == expected_data + + @pytest.mark.parametrize("span_streaming", [True, False]) def test_template_tracing_meta( sentry_init,