From 5a46e1f57a49e94ffb97cb35896f61414e26e314 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 25 Jun 2026 14:37:22 -0400 Subject: [PATCH] fix(httpx): Gate url.full, url.query, and url.fragment behind send_default_pii These URL attributes can contain PII (query params, fragments), so they should only be set when send_default_pii is enabled, consistent with the aiohttp and wsgi integrations. Refs https://github.com/getsentry/sentry-python/issues/2558 Co-Authored-By: Claude Sonnet 4.6 --- sentry_sdk/integrations/httpx.py | 5 +++-- tests/integrations/httpx/test_httpx.py | 28 +++++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/sentry_sdk/integrations/httpx.py b/sentry_sdk/integrations/httpx.py index 5e605de46d..a68f20b299 100644 --- a/sentry_sdk/integrations/httpx.py +++ b/sentry_sdk/integrations/httpx.py @@ -3,6 +3,7 @@ import sentry_sdk from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.scope import should_send_default_pii from sentry_sdk.tracing import BAGGAGE_HEADER_NAME from sentry_sdk.tracing_utils import ( add_http_request_source, @@ -73,7 +74,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response": ) as streamed_span: attributes: "Attributes" = {} - if parsed_url is not None: + if parsed_url is not None and should_send_default_pii(): attributes["url.full"] = parsed_url.url if parsed_url.query: attributes["url.query"] = parsed_url.query @@ -183,7 +184,7 @@ async def send( ) as streamed_span: attributes: "Attributes" = {} - if parsed_url is not None: + if parsed_url is not None and should_send_default_pii(): attributes["url.full"] = parsed_url.url if parsed_url.query: attributes["url.query"] = parsed_url.query diff --git a/tests/integrations/httpx/test_httpx.py b/tests/integrations/httpx/test_httpx.py index c4462c8e99..4ca18c8edc 100644 --- a/tests/integrations/httpx/test_httpx.py +++ b/tests/integrations/httpx/test_httpx.py @@ -1119,18 +1119,20 @@ def test_span_origin_span_streaming( assert http_span["attributes"]["sentry.origin"] == "auto.http.httpx" +@pytest.mark.parametrize("send_default_pii", [True, False]) @pytest.mark.parametrize( "httpx_client", (httpx.Client(), httpx.AsyncClient()), ) def test_http_url_attributes_span_streaming( - sentry_init, capture_items, httpx_client, httpx_mock + sentry_init, capture_items, httpx_client, httpx_mock, send_default_pii ): httpx_mock.add_response() sentry_init( integrations=[HttpxIntegration()], traces_sample_rate=1.0, + send_default_pii=send_default_pii, _experiments={"trace_lifecycle": "stream"}, ) @@ -1148,24 +1150,32 @@ def test_http_url_attributes_span_streaming( http_span = _get_http_client_span(items) assert http_span["attributes"]["http.request.method"] == "GET" - assert http_span["attributes"]["url.full"] == "http://example.com/" - assert http_span["attributes"]["url.query"] == "foo=bar" - assert http_span["attributes"]["url.fragment"] == "frag" assert http_span["attributes"]["http.response.status_code"] == 200 + if send_default_pii: + assert http_span["attributes"]["url.full"] == "http://example.com/" + assert http_span["attributes"]["url.query"] == "foo=bar" + assert http_span["attributes"]["url.fragment"] == "frag" + else: + assert "url.full" not in http_span["attributes"] + assert "url.query" not in http_span["attributes"] + assert "url.fragment" not in http_span["attributes"] + +@pytest.mark.parametrize("send_default_pii", [True, False]) @pytest.mark.parametrize( "httpx_client", (httpx.Client(), httpx.AsyncClient()), ) def test_http_url_attributes_no_query_or_fragment_span_streaming( - sentry_init, capture_items, httpx_client, httpx_mock + sentry_init, capture_items, httpx_client, httpx_mock, send_default_pii ): httpx_mock.add_response() sentry_init( integrations=[HttpxIntegration()], traces_sample_rate=1.0, + send_default_pii=send_default_pii, _experiments={"trace_lifecycle": "stream"}, ) @@ -1183,7 +1193,11 @@ def test_http_url_attributes_no_query_or_fragment_span_streaming( http_span = _get_http_client_span(items) assert http_span["attributes"]["http.request.method"] == "GET" - assert http_span["attributes"]["url.full"] == "http://example.com/" + assert http_span["attributes"]["http.response.status_code"] == 200 assert "url.query" not in http_span["attributes"] assert "url.fragment" not in http_span["attributes"] - assert http_span["attributes"]["http.response.status_code"] == 200 + + if send_default_pii: + assert http_span["attributes"]["url.full"] == "http://example.com/" + else: + assert "url.full" not in http_span["attributes"]