From 8100f648191570d033f92e9bcf56e7f43449204e Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Thu, 28 May 2026 11:56:54 -0300 Subject: [PATCH 1/2] fix(api): Hide internal span fields Use the API-facing span attribute visibility helper for the legacy spans fields endpoint so internal Sentry convention attributes are not exposed to non-staff users. Refs EXP-973 Co-Authored-By: Codex --- src/sentry/api/endpoints/organization_spans_fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sentry/api/endpoints/organization_spans_fields.py b/src/sentry/api/endpoints/organization_spans_fields.py index c62c269c172a5d..a3e61cf783f217 100644 --- a/src/sentry/api/endpoints/organization_spans_fields.py +++ b/src/sentry/api/endpoints/organization_spans_fields.py @@ -29,7 +29,7 @@ from sentry.search.eap.resolver import SearchResolver from sentry.search.eap.spans.definitions import SPAN_DEFINITIONS from sentry.search.eap.types import SearchResolverConfig, SupportedTraceItemType -from sentry.search.eap.utils import can_expose_attribute, translate_internal_to_public_alias +from sentry.search.eap.utils import can_expose_attribute_to_api, translate_internal_to_public_alias from sentry.search.events.types import SnubaParams from sentry.snuba.referrer import Referrer from sentry.tagstore.types import TagValue @@ -125,7 +125,7 @@ def get(self, request: Request, organization: Organization) -> Response: as_tag_key(attribute.name, serialized["type"]) for attribute in rpc_response.attributes if attribute.name - and can_expose_attribute( + and can_expose_attribute_to_api( attribute.name, SupportedTraceItemType.SPANS, include_internal=include_internal, From 524f459554cabcd8dd7ff84cdb734e68ac2441b2 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Thu, 28 May 2026 11:57:18 -0300 Subject: [PATCH 2/2] test(api): Cover hidden internal span fields Add endpoint coverage for the legacy spans fields API so internal Sentry convention attributes remain hidden while public attributes are returned. Refs EXP-973 Co-Authored-By: Codex --- .../test_organization_spans_fields.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/sentry/api/endpoints/test_organization_spans_fields.py b/tests/sentry/api/endpoints/test_organization_spans_fields.py index 567df7b5bcc96e..e5778be9dc75dc 100644 --- a/tests/sentry/api/endpoints/test_organization_spans_fields.py +++ b/tests/sentry/api/endpoints/test_organization_spans_fields.py @@ -3,6 +3,11 @@ from uuid import uuid4 from django.urls import reverse +from sentry_conventions.attributes import ATTRIBUTE_NAMES +from sentry_protos.snuba.v1.endpoint_trace_item_attributes_pb2 import ( + TraceItemAttributeNamesResponse, +) +from sentry_protos.snuba.v1.trace_item_attribute_pb2 import AttributeKey from sentry.exceptions import InvalidSearchQuery from sentry.testutils.cases import APITestCase, BaseSpansTestCase, SpanTestCase @@ -163,6 +168,27 @@ def test_boolean_attributes(self) -> None: assert "tags[is_debug,boolean]" in keys assert "tags[is_production,boolean]" in keys + @mock.patch("sentry.api.endpoints.organization_spans_fields.snuba_rpc.attribute_names_rpc") + def test_internal_sentry_convention_attributes_are_hidden(self, mock_attribute_names) -> None: + self.create_span(start_ts=before_now(days=0, minutes=10)) + mock_attribute_names.return_value = TraceItemAttributeNamesResponse( + attributes=[ + TraceItemAttributeNamesResponse.Attribute( + name="public.attribute", + type=AttributeKey.Type.TYPE_STRING, + ), + TraceItemAttributeNamesResponse.Attribute( + name=ATTRIBUTE_NAMES.SENTRY_DSC_ENVIRONMENT, + type=AttributeKey.Type.TYPE_STRING, + ), + ] + ) + + response = self.do_request() + + assert response.status_code == 200, response.data + assert response.data == [{"key": "public.attribute", "name": "public.attribute"}] + class OrganizationSpansTagKeyValuesEndpointTest(BaseSpansTestCase, APITestCase): view = "sentry-api-0-organization-spans-fields-values"