Skip to content

Commit dd3e8f8

Browse files
committed
feat(sanic): Add sentry.is_localhost for inbound filters
1 parent adf9025 commit dd3e8f8

4 files changed

Lines changed: 69 additions & 1 deletion

File tree

‎sentry_sdk/integrations/sanic.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
ensure_integration_enabled,
2626
event_from_exception,
2727
has_data_collection_enabled,
28+
is_localhost,
2829
parse_version,
2930
reraise,
3031
)
@@ -182,6 +183,16 @@ async def _context_enter(request: "Request") -> None:
182183
scope.clear_breadcrumbs()
183184
scope.add_event_processor(_make_request_processor(weak_request))
184185

186+
scope.set_attribute(
187+
SPANDATA.SENTRY_IS_LOCALHOST,
188+
is_localhost(
189+
client_ip=request.remote_addr or None,
190+
url_host=urlsplit(request.url).hostname,
191+
host_header=request.headers.get("host"),
192+
forwarded_host_header=request.headers.get("x-forwarded-host"),
193+
),
194+
)
195+
185196
if is_span_streaming_enabled:
186197
integration = client.get_integration(SanicIntegration)
187198
if (

‎sentry_sdk/utils.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2229,7 +2229,7 @@ def _is_localhost(
22292229
22302230
Based on the logic in relay's localhost filter.
22312231
"""
2232-
if client_ip is not None and client_ip in _LOCAL_IPS:
2232+
if client_ip is not None and client_ip.strip("[]") in _LOCAL_IPS:
22332233
return True
22342234

22352235
# URL host uses subdomain-aware matching

‎tests/integrations/sanic/test_sanic.py‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,3 +889,57 @@ def test_remote_addr_data_collection(
889889
assert event["request"]["env"] == {"REMOTE_ADDR": ""}
890890
else:
891891
assert "env" not in event["request"]
892+
893+
894+
@pytest.mark.skipif(
895+
not PERFORMANCE_SUPPORTED, reason="Performance not supported on this Sanic version"
896+
)
897+
@pytest.mark.parametrize(
898+
"forwarded_for, host_header, is_localhost",
899+
[
900+
# Loopback IP
901+
("127.0.0.1", "example.com", True),
902+
# IPv6 loopback (quoted brackets per RFC 7239)
903+
('"[::1]"', "example.com", True),
904+
# Localhost host header with non-local IP
905+
("203.0.113.50", "localhost:8000", True),
906+
# Non-local everything
907+
("203.0.113.50", "example.com", False),
908+
],
909+
)
910+
def test_is_localhost_span_attribute(
911+
sentry_init, app, capture_items, forwarded_for, host_header, is_localhost
912+
):
913+
app.config.FORWARDED_SECRET = "test"
914+
915+
@app.route("/child-span")
916+
def child_span_handler_localhost(request):
917+
with sentry_sdk.traces.start_span(name="child-span"):
918+
pass
919+
return response.text("ok")
920+
921+
sentry_init(
922+
integrations=[SanicIntegration()],
923+
default_integrations=False,
924+
traces_sample_rate=1.0,
925+
trace_lifecycle="stream",
926+
)
927+
928+
items = capture_items("span")
929+
930+
c = get_client(app)
931+
with c as client:
932+
client.get(
933+
"/child-span",
934+
headers={
935+
"Forwarded": f"for={forwarded_for};secret=test",
936+
"Host": host_header,
937+
},
938+
)
939+
940+
sentry_sdk.flush()
941+
942+
child_span, server_span = [item.payload for item in items]
943+
944+
assert server_span["attributes"]["sentry.is_localhost"] is is_localhost
945+
assert child_span["attributes"]["sentry.is_localhost"] is is_localhost

‎tests/test_utils.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,9 @@ def test_package_version_is_none():
14221422
({"client_ip": "10.0.0.1"}, False),
14231423
({"client_ip": "8.8.8.8"}, False),
14241424
({"client_ip": "::2"}, False),
1425+
# Bracketed IPv6 (e.g. from Sanic's Forwarded header parsing)
1426+
({"client_ip": "[::1]"}, True),
1427+
({"client_ip": "[::2]"}, False),
14251428
# URL host (subdomain-aware matching)
14261429
({"url_host": "localhost"}, True),
14271430
({"url_host": "127.0.0.1"}, True),

0 commit comments

Comments
 (0)