From fa7427c63384eb5e6582a1c1feb4c8de00b5e4b9 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 30 Sep 2026 09:47:48 +0200 Subject: [PATCH 1/3] feat(wsgi): Add sentry.is_localhost for inbound filters --- sentry_sdk/integrations/wsgi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index ab04308f6e..f541b0aae6 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -131,9 +131,7 @@ def __call__( SPANDATA.SENTRY_IS_LOCALHOST, _is_localhost( client_ip=get_client_ip(environ), - url_host=environ.get("SERVER_NAME") - if not environ.get("HTTP_HOST") - else None, + url_host=environ.get("SERVER_NAME"), host_header=environ.get("HTTP_HOST"), forwarded_host_header=environ.get("HTTP_X_FORWARDED_HOST"), ), From e53383f278b4e4b06599c58513e48d3d0571e757 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 30 Sep 2026 10:04:12 +0200 Subject: [PATCH 2/3] feat(asgi): Add `sentry.is_localhost` for inbound filters --- sentry_sdk/integrations/wsgi.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index f541b0aae6..ab04308f6e 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -131,7 +131,9 @@ def __call__( SPANDATA.SENTRY_IS_LOCALHOST, _is_localhost( client_ip=get_client_ip(environ), - url_host=environ.get("SERVER_NAME"), + url_host=environ.get("SERVER_NAME") + if not environ.get("HTTP_HOST") + else None, host_header=environ.get("HTTP_HOST"), forwarded_host_header=environ.get("HTTP_X_FORWARDED_HOST"), ), From 6b8f393daec308e5a705247927cc01035e7619af Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 30 Sep 2026 10:45:14 +0200 Subject: [PATCH 3/3] feat(aiohttp): Add `sentry.is_localhost` for inbound filters --- sentry_sdk/integrations/aiohttp.py | 23 +++++++++ tests/integrations/aiohttp/test_aiohttp.py | 56 ++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 4f7b421357..1ae4d47eb2 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -46,6 +46,7 @@ AnnotatedValue, _get_aws_sigv4_signed_headers_from_authorization_header, _get_aws_sigv4_signed_headers_from_url_query_string, + _is_localhost, _register_control_flow_exception, capture_internal_exceptions, ensure_integration_enabled, @@ -146,6 +147,17 @@ async def sentry_app_handle( scope.clear_breadcrumbs() scope.add_event_processor(_make_request_processor(weak_request)) + scope.set_attribute( + SPANDATA.SENTRY_IS_LOCALHOST, + _is_localhost( + client_ip=_get_client_ip(request), + host_header=request.headers.get("Host"), + forwarded_host_header=request.headers.get( + "X-Forwarded-Host" + ), + ), + ) + headers = dict(request.headers) span_ctx: "ContextManager[Union[Span, StreamedSpan]]" @@ -643,6 +655,17 @@ def aiohttp_processor( return aiohttp_processor +def _get_client_ip(request: "Request") -> "Optional[str]": + x_forwarded_for = request.headers.get("X-Forwarded-For", "").split(",")[0].strip() + if x_forwarded_for: + return x_forwarded_for + + if request.headers.get("X-Real-IP"): + return request.headers["X-Real-IP"] + + return request.remote + + def _capture_exception() -> "ExcInfo": exc_info = sys.exc_info() event, hint = event_from_exception( diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index 1c3166de64..b4301ad628 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -2416,6 +2416,62 @@ async def hello(request): assert event["request"]["query_string"] == expected_query +@pytest.mark.asyncio +@pytest.mark.parametrize( + "forwarded_for, host_header, is_localhost", + [ + pytest.param(None, None, True, id="loopback-ip"), + pytest.param( + "93.184.216.34", "localhost:8080", True, id="localhost-host-header" + ), + pytest.param("93.184.216.34", "example.com", False, id="non-local"), + ], +) +async def test_is_localhost_attribute( + sentry_init, + aiohttp_client, + capture_items, + forwarded_for, + host_header, + is_localhost, +): + sentry_init( + integrations=[AioHttpIntegration()], + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) + + async def hello(request): + with sentry_sdk.traces.start_span(name="child"): + pass + return web.Response(text="hello") + + app = web.Application() + app.router.add_get("/", hello) + + items = capture_items("span") + + client = await aiohttp_client(app) + headers = {} + if host_header: + headers["Host"] = host_header + if forwarded_for: + headers["X-Forwarded-For"] = forwarded_for + resp = await client.get("/", headers=headers) + assert resp.status == 200 + + sentry_sdk.flush() + + child_spans = [item.payload for item in items if not item.payload.get("is_segment")] + server_spans = [item.payload for item in items if item.payload.get("is_segment")] + + assert len(server_spans) == 1 + assert len(child_spans) == 1 + + assert server_spans[0]["attributes"][SPANDATA.SENTRY_IS_LOCALHOST] == is_localhost + assert child_spans[0]["attributes"][SPANDATA.SENTRY_IS_LOCALHOST] == is_localhost + + @pytest.mark.asyncio @pytest.mark.parametrize( "init_kwargs, expect_remote_addr", DATA_COLLECTION_REMOTE_ADDR_CASES