Repository navigation
feat: Add sentry.is_localhost to server spans - #7767
sentrivana wants to merge 0 commit into
Conversation
Codecov Results 📊✅ 129639 passed | ❌ 1 failed | ⏭️ 7169 skipped | Total: 136809 | Pass Rate: 94.76% | Execution Time: 436m 43s 📊 Comparison with Base Branch
➕ New Tests (1)View new tests
❌ Failed Tests
|
| File | Patch % | Lines |
|---|---|---|
| sentry_sdk/integrations/wsgi.py | 100.00% | |
| sentry_sdk/utils.py | 90.00% |
Coverage diff
@@ Coverage Diff @@
## master #PR +/-##
==========================================
+ Coverage 90.25% 90.29% +0.04%
==========================================
Files 195 195 —
Lines 26195 26221 +26
Branches 9758 9768 +10
==========================================
+ Hits 23641 23674 +33
- Misses 2554 2547 -7
- Partials 1484 1483 -1Generated by Codecov Action
| server = asgi_scope.get("server") | ||
| if server is not None: | ||
| attributes["server.address"] = server[0] | ||
| if server[1] is not None: | ||
| attributes["server.port"] = server[1] |
There was a problem hiding this comment.
No tests for new ASGI server attributes and localhost marker
Please add ASGI tests for extracting server.address and server.port from the scope, including a Unix-socket case where the port is None. Also assert that the sentry.is_localhost scope attribute is set from the extracted address and inherited by associated spans.
Evidence
_get_request_attributes()copiesserver[0]toserver.addressand emitsserver.portonly whenserver[1]is notNone.SentryAsgiMiddleware._run_app()uses the extracted address to setsentry.is_localhoston the scope for streamed spans.tests/integrations/asgi/test_asgi.pyhas no assertions for these server attributes or the localhost marker, including no Unix-socket case.
Identified by Warden · code-review · WYG-ABN
| capture_internal_exceptions, | ||
| event_from_exception, | ||
| has_data_collection_enabled, | ||
| is_localhost, |
There was a problem hiding this comment.
Wildcard bind address is classified as localhost
In ASGI/WSGI streaming mode, 0.0.0.0 can be used as server.address and is classified as localhost. Production apps bound to all interfaces may therefore be marked sentry.is_localhost=true; if an inbound filter uses that attribute, it can drop their events. Treat the wildcard bind address separately from loopback addresses.
Evidence
_get_request_attributesin_asgi_common.pysetsserver.addressfromscope["server"][0].- In the ASGI middleware, the localhost attribute is set when the request span is a
StreamedSpan; WSGI has the corresponding streamed-span path. utils.is_localhostexplicitly includes0.0.0.0in_LOCALHOST_NAMESand returnsTruebefore checking IP loopback status.SPANDATA.SENTRY_IS_LOCALHOSTis documented inconsts.pyas being used in inbound filters.
Identified by Warden · code-review · PNP-J3M
| if SPANDATA.SERVER_ADDRESS in request_attributes: | ||
| sentry_scope.set_attribute( | ||
| SPANDATA.SENTRY_IS_LOCALHOST, | ||
| is_localhost( | ||
| request_attributes[SPANDATA.SERVER_ADDRESS] | ||
| ), | ||
| ) |
There was a problem hiding this comment.
ASGI bind address can classify production server spans as localhost
When trace_lifecycle="stream" is enabled, the ASGI middleware derives sentry.is_localhost from scope["server"][0], which is the server-side address rather than the request’s Host. Production traffic can therefore be marked localhost when the app binds to 0.0.0.0 or to loopback behind a proxy, potentially causing inbound filters to drop it. Consider deriving this classification from the request-facing host or otherwise distinguishing bind addresses from localhost.
Evidence
_get_request_attributes()insentry_sdk/integrations/_asgi_common.pysetsserver.addressfrom the first value of the ASGIservertuple.- In
SentryAsgiMiddleware._run_app(), theStreamedSpanpath passes thatserver.addresstois_localhost()and setssentry.is_localhoston the isolation scope; this path is used when span streaming is enabled. is_localhost()explicitly treats0.0.0.0and loopback addresses as local, including cases where a proxy forwards production requests to a loopback-bound app.SPANDATA.SENTRY_IS_LOCALHOSTis documented as being used in inbound filters, so this false classification can affect production telemetry.
Identified by Warden · code-review · PWJ-GAU
| capture_internal_exceptions, | ||
| event_from_exception, | ||
| has_data_collection_enabled, | ||
| is_localhost, |
There was a problem hiding this comment.
Wildcard bind address is incorrectly flagged as localhost
is_localhost() explicitly treats 0.0.0.0 as localhost. With ASGI trace streaming enabled, the server address from scope["server"][0] is passed to this helper, so an app bound to all interfaces can set sentry.is_localhost=true on its server span and trigger localhost inbound filtering for production telemetry. Treat the unspecified wildcard address as non-local while retaining the checks for actual localhost names and loopback addresses.
Evidence
utils.is_localhost()returnsTruefor0.0.0.0before checkingip.is_loopback; that address is unspecified, not loopback._get_request_attributes()in_asgi_common.pycopiesscope["server"][0]toserver.address.- In
SentryAsgiMiddleware._run_app(), streamed spans passserver.addresstois_localhost()and setSPANDATA.SENTRY_IS_LOCALHOST; this path runs when span streaming is enabled. SPANDATA.SENTRY_IS_LOCALHOSTinconsts.pyis documented as being used in inbound filters.
Identified by Warden · code-review · AK7-SWG
| if SPANDATA.SERVER_ADDRESS in request_attributes: | ||
| scope.set_attribute( | ||
| SPANDATA.SENTRY_IS_LOCALHOST, | ||
| is_localhost( | ||
| request_attributes[SPANDATA.SERVER_ADDRESS] | ||
| ), | ||
| ) | ||
|
|
There was a problem hiding this comment.
No tests for sentry.is_localhost or is_localhost()
Please add unit tests for is_localhost() edge cases and a WSGI integration test asserting sentry.is_localhost is set on the isolation scope/spans when span streaming is enabled.
Evidence
- PR body explicitly lists
Also TODO: tests. grepovertests/**/*.pyfinds no coverage ofis_localhost,SENTRY_IS_LOCALHOST, orsentry.is_localhost.- New behavior in this hunk sets
SPANDATA.SENTRY_IS_LOCALHOSTfromrequest_attributes[SPANDATA.SERVER_ADDRESS]only on theStreamedSpanpath.
Identified by Warden · code-review · KKZ-3U5
| is_localhost( | ||
| request_attributes[SPANDATA.SERVER_ADDRESS] | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Wildcard bind address is classified as localhost
When server.address is 0.0.0.0, is_localhost() marks the span as local even though this is an unspecified all-interfaces address, not a loopback address. ASGI or WSGI deployments reporting this address can therefore have production spans marked local and affected by inbound filters. Consider limiting this classification to loopback addresses.
Evidence
is_localhost()includes0.0.0.0in_LOCALHOST_NAMESand returnsTruebefore checkingip.is_loopback.- ASGI request attributes take
server.addressfrom the ASGI scope’s server tuple; WSGI takes it fromSERVER_NAME. - The ASGI and WSGI integrations pass that address to
is_localhost()and setsentry.is_localhostfrom the result. SPANDATA.SENTRY_IS_LOCALHOSTis documented as used in inbound filters.
Identified by Warden · code-review · EF8-X9G
| @@ -1,4 +1,5 @@ | |||
| import copy | |||
| import ipaddress | |||
There was a problem hiding this comment.
Wildcard ASGI bind address is classified as localhost
When span streaming is enabled, ASGI requests served by a server bound to 0.0.0.0 are marked sentry.is_localhost=true: the middleware passes server.address to is_localhost(), which explicitly classifies 0.0.0.0 as localhost. Since this is an unspecified bind address, not a loopback address, the flag can cause production requests to be dropped by a configured localhost inbound filter. Consider excluding 0.0.0.0 from localhost detection.
Evidence
_get_request_attributes()copiesasgi_scope["server"][0]intoserver.address.- In the
StreamedSpanpath,SentryAsgiMiddleware._run_app()passes that value tois_localhost()and setssentry.is_localhoston the isolation scope. is_localhost()explicitly returnsTruefor0.0.0.0, andSPANDATA.SENTRY_IS_LOCALHOSTis documented as being used in inbound filters.
Identified by Warden · code-review · TXD-3R9
| _LOCALHOST_NAMES = {"localhost", "127.0.0.1", "::1", "0.0.0.0", "0:0:0:0:0:0:0:1"} | ||
|
|
||
| if address in _LOCALHOST_NAMES: | ||
| return True |
There was a problem hiding this comment.
0.0.0.0 is classified as localhost for server spans
When span streaming is enabled, WSGI and ASGI use is_localhost on the extracted server.address. Since is_localhost explicitly treats 0.0.0.0 as localhost—even though it is an unspecified bind-all address—deployments that report that listen address can mark production server spans as local. This may cause inbound localhost filters to discard those spans. Please remove 0.0.0.0 from the localhost set.
Evidence
is_localhostexplicitly returnsTruefor0.0.0.0, bypassing the subsequentip.is_loopbackcheck; the address is unspecified, not loopback.- In the span-streaming path, WSGI extracts
SERVER_NAMEasserver.addressand ASGI extractsscope["server"][0]; each passes that value tois_localhostand setsSPANDATA.SENTRY_IS_LOCALHOSTon the scope. SPANDATA.SENTRY_IS_LOCALHOSTis documented as used in inbound filters, so a production server reporting0.0.0.0can be classified as local for filtering.
Identified by Warden · code-review · CW6-JPB
| @@ -1,4 +1,5 @@ | |||
| import copy | |||
| import ipaddress | |||
There was a problem hiding this comment.
is_localhost treats 0.0.0.0 as localhost
is_localhost() (utils.py ~2216) hardcodes 0.0.0.0 as local even though it is an unspecified bind-all address, not loopback—ASGI/WSGI streamed spans that report that listen address can set sentry.is_localhost=true and be dropped by inbound filters. Remove 0.0.0.0 and rely on ip.is_loopback plus localhost names.
Evidence
- The new
import ipaddresssupportsis_localhost()insentry_sdk/utils.py, which includes0.0.0.0in_LOCALHOST_NAMESand returnsTruebefore any loopback check. ipaddress.ip_address("0.0.0.0").is_loopbackisFalse; the address is unspecified, not loopback.- ASGI
_get_request_attributes()setsserver.addressfromscope["server"][0](the listen/bind host), commonly0.0.0.0in Docker/uvicorn/gunicorn. - ASGI and WSGI stream paths call
is_localhost(server.address)and setSPANDATA.SENTRY_IS_LOCALHOSTon the isolation scope; that attribute is documented for inbound filters.
Also found at 4 additional locations
sentry_sdk/integrations/asgi.py:51sentry_sdk/integrations/asgi.py:349-351sentry_sdk/integrations/wsgi.py:194-196sentry_sdk/utils.py:2216-2219
Identified by Warden · find-bugs · 9ZH-U3Y
| if SPANDATA.SERVER_ADDRESS in request_attributes: | ||
| scope.set_attribute( | ||
| SPANDATA.SENTRY_IS_LOCALHOST, | ||
| is_localhost( | ||
| request_attributes[SPANDATA.SERVER_ADDRESS] | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Localhost flag derived from internal SERVER_NAME
This path derives sentry.is_localhost from SERVER_NAME/server.address (internal server address), so reverse-proxied apps listening on loopback can mark production spans local; use the request Host (HTTP_HOST/get_host()) instead.
Evidence
- The streamed-span path sets
sentry.is_localhostonly fromrequest_attributes[SPANDATA.SERVER_ADDRESS]. - WSGI
_get_request_attributes()copies rawenviron["SERVER_NAME"]intoserver.address, notHTTP_HOST. _werkzeug.get_host()documents thatSERVER_NAME/SERVER_PORTdescribe the internal server and prefersHTTP_HOSTwhen building the public host.- A production app behind a proxy with internal
SERVER_NAMEof127.0.0.1therefore getsis_localhost() == Trueand inherits that scope attribute on spans.
Identified by Warden · find-bugs · RD5-R97
eee3388 to
2a499ec
Compare
Description
server.addressas a span attribute. Use it to setsentry.is_localhoston scope so that all associated spans inherit it.server.addressfrom the ASGI scope. Added that and using it to setsentry.is_localhost.Also TODO: tests
Issues
Closes https://linear.app/getsentry/issue/PY-2891/implement-sentryis-localhost
Reminders
uv run ruff.feat:,fix:,ref:,meta:)