@@ -1490,3 +1490,73 @@ async def wrapped_app(scope, receive, send):
14901490 else :
14911491 assert "user.ip_address" not in server_span ["attributes" ]
14921492 assert "user.ip_address" not in child_span ["attributes" ]
1493+
1494+
1495+ @pytest .mark .parametrize (
1496+ "client_ip, server, host_header, is_localhost" ,
1497+ [
1498+ # Loopback IP
1499+ ("127.0.0.1" , ("example.com" , 80 ), b"example.com" , True ),
1500+ # IPv6 loopback
1501+ ("::1" , ("example.com" , 80 ), b"example.com" , True ),
1502+ # Localhost host header with non-local IP
1503+ ("203.0.113.50" , ("example.com" , 80 ), b"localhost:8000" , True ),
1504+ # Server bound to localhost
1505+ ("203.0.113.50" , ("localhost" , 8000 ), b"example.com" , True ),
1506+ # Non-local everything
1507+ ("203.0.113.50" , ("example.com" , 80 ), b"example.com" , False ),
1508+ ],
1509+ )
1510+ @pytest .mark .asyncio
1511+ async def test_is_localhost_attribute (
1512+ sentry_init ,
1513+ capture_items ,
1514+ client_ip ,
1515+ server ,
1516+ host_header ,
1517+ is_localhost ,
1518+ ):
1519+ async def app (scope , receive , send ):
1520+ if scope ["type" ] == "lifespan" :
1521+ while True :
1522+ message = await receive ()
1523+ if message ["type" ] == "lifespan.startup" :
1524+ await send ({"type" : "lifespan.startup.complete" })
1525+ elif message ["type" ] == "lifespan.shutdown" :
1526+ await send ({"type" : "lifespan.shutdown.complete" })
1527+ return
1528+
1529+ with sentry_sdk .traces .start_span (name = "child-span" ):
1530+ pass
1531+
1532+ await send (
1533+ {
1534+ "type" : "http.response.start" ,
1535+ "status" : 200 ,
1536+ "headers" : [[b"content-type" , b"text/plain" ]],
1537+ }
1538+ )
1539+ await send ({"type" : "http.response.body" , "body" : b"Hello, world!" })
1540+
1541+ sentry_init (traces_sample_rate = 1.0 , trace_lifecycle = "stream" )
1542+ sentry_app = SentryAsgiMiddleware (app )
1543+
1544+ async def wrapped_app (scope , receive , send ):
1545+ if scope ["type" ] != "lifespan" :
1546+ scope ["client" ] = (client_ip , 0 )
1547+ scope ["server" ] = server
1548+ scope ["headers" ] = [(k , v ) for k , v in scope ["headers" ] if k != b"host" ]
1549+ scope ["headers" ].append ((b"host" , host_header ))
1550+
1551+ await sentry_app (scope , receive , send )
1552+
1553+ async with TestClient (wrapped_app ) as client :
1554+ items = capture_items ("span" )
1555+ await client .get ("/some_url" )
1556+
1557+ sentry_sdk .flush ()
1558+
1559+ child_span , server_span = [item .payload for item in items ]
1560+
1561+ assert server_span ["attributes" ]["sentry.is_localhost" ] is is_localhost
1562+ assert child_span ["attributes" ]["sentry.is_localhost" ] is is_localhost
0 commit comments