From bd7de91545632bf422297171052385f3b6917b51 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Wed, 24 Jun 2026 11:40:47 -0400 Subject: [PATCH] fix(wsgi): Gate url.full, url.path, and http.query behind send_default_pii The url.full, url.path, and http.query span attributes can contain user-provided query parameters and paths that may include PII. Gate these behind the send_default_pii setting, consistent with how client.address is handled. Fixes PY-2552 Co-Authored-By: Claude Sonnet 4.6 --- sentry_sdk/integrations/wsgi.py | 16 ++++++++++------ tests/integrations/wsgi/test_wsgi.py | 23 +++++++++++++++++++---- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index ff27f86f1b..e776ed915a 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -394,12 +394,6 @@ def _get_request_attributes( for header, value in headers.items(): attributes[f"http.request.header.{header.lower()}"] = value - query_string = environ.get("QUERY_STRING") - if query_string: - attributes["http.query"] = query_string - - attributes["url.full"] = get_request_url(environ, use_x_forwarded_for) - url_scheme = environ.get("wsgi.url_scheme") if url_scheme: attributes["network.protocol.name"] = url_scheme @@ -420,4 +414,14 @@ def _get_request_attributes( if client_ip: attributes["client.address"] = client_ip + query_string = environ.get("QUERY_STRING") + if query_string: + attributes["http.query"] = query_string + + path = environ.get("PATH_INFO", "") + if path: + attributes["url.path"] = path + + attributes["url.full"] = get_request_url(environ, use_x_forwarded_for) + return attributes diff --git a/tests/integrations/wsgi/test_wsgi.py b/tests/integrations/wsgi/test_wsgi.py index 2586d30d4e..3b684adb0d 100644 --- a/tests/integrations/wsgi/test_wsgi.py +++ b/tests/integrations/wsgi/test_wsgi.py @@ -208,6 +208,7 @@ def dogpark(environ, start_response): assert envelope["request"] == error_event["request"] +@pytest.mark.parametrize("send_pii", [True, False]) @pytest.mark.parametrize("span_streaming", [True, False]) def test_transaction_no_error( sentry_init, @@ -215,13 +216,14 @@ def test_transaction_no_error( capture_items, DictionaryContaining, # noqa:N803 span_streaming, + send_pii, ): def dogpark(environ, start_response): start_response("200 OK", []) return ["Go get the ball! Good dog!"] sentry_init( - send_default_pii=True, + send_default_pii=send_pii, traces_sample_rate=1.0, _experiments={ "trace_lifecycle": "stream" if span_streaming else "static", @@ -235,7 +237,7 @@ def dogpark(environ, start_response): else: events = capture_events() - client.get("/dogs/are/great/") + client.get("/dogs/are/great?toy=tennisball") sentry_sdk.flush() @@ -248,9 +250,18 @@ def dogpark(environ, start_response): assert span["attributes"]["sentry.op"] == "http.server" assert span["attributes"]["sentry.span.source"] == "route" assert span["attributes"]["http.request.method"] == "GET" - assert span["attributes"]["url.full"] == "http://localhost/dogs/are/great/" assert span["attributes"]["http.response.status_code"] == 200 assert span["status"] == "ok" + + if send_pii: + assert span["attributes"]["url.full"] == "http://localhost/dogs/are/great" + assert span["attributes"]["url.path"] == "/dogs/are/great" + assert span["attributes"]["http.query"] == "toy=tennisball" + else: + assert "url.path" not in span["attributes"] + assert "url.full" not in span["attributes"] + assert "http.query" not in span["attributes"] + else: envelope = events[0] @@ -258,7 +269,11 @@ def dogpark(environ, start_response): assert envelope["transaction"] == "generic WSGI request" assert envelope["contexts"]["trace"]["op"] == "http.server" assert envelope["request"] == DictionaryContaining( - {"method": "GET", "url": "http://localhost/dogs/are/great/"} + { + "method": "GET", + "url": "http://localhost/dogs/are/great", + "query_string": "toy=tennisball", + } )