From 05891e1f734a947395bbfb7590da5a870e77a676 Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Tue, 26 May 2026 16:26:49 -0400 Subject: [PATCH 1/2] ref(replays): Remove unused data export notifications endpoint This endpoint was intended as the HTTP push target for a Google Pub/Sub subscription on Storage Transfer Service job-completion events, so that failed bucket-to-bucket transfers could be retried automatically. In practice it has never received Pub/Sub traffic (90d telemetry shows no spans on the transaction) and the replay export pipeline that would drive it is not wired into the EU export orchestrator in getsentry. The endpoint was gated only by SentryIsAuthenticated, which is the wrong check for a Pub/Sub push (Google does not have a Sentry session) and admits any authenticated Sentry user, who could forge a Pub/Sub-shaped payload and trigger a Storage Transfer job rerun against jobs in the configured GCP project. The handler is generic STS plumbing and is not replay-specific despite its module path; profile exports use the same underlying helpers, which remain in src/sentry/replays/data_export.py and are unaffected by this change. Removing the endpoint eliminates the unauthenticated path entirely. If the retry workflow is needed in the future, a new endpoint should be introduced with proper Google OIDC token verification. Refs REPLAY-920 Refs VULN-1598 Co-Authored-By: Claude --- src/sentry/api/urls.py | 6 ---- .../endpoints/data_export_notifications.py | 25 ------------- .../test_data_export_notifications.py | 35 ------------------- 3 files changed, 66 deletions(-) delete mode 100644 src/sentry/replays/endpoints/data_export_notifications.py delete mode 100644 tests/sentry/replays/endpoints/test_data_export_notifications.py diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index f25e1b58d741db..3a2f581a9ac433 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -489,7 +489,6 @@ from sentry.relocation.api.endpoints.recover import RelocationRecoverEndpoint from sentry.relocation.api.endpoints.retry import RelocationRetryEndpoint from sentry.relocation.api.endpoints.unpause import RelocationUnpauseEndpoint -from sentry.replays.endpoints.data_export_notifications import DataExportNotificationsEndpoint from sentry.replays.endpoints.organization_replay_count import OrganizationReplayCountEndpoint from sentry.replays.endpoints.organization_replay_details import OrganizationReplayDetailsEndpoint from sentry.replays.endpoints.organization_replay_events_meta import ( @@ -3795,11 +3794,6 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]: AcceptOrganizationInvite.as_view(), name="sentry-api-0-organization-accept-organization-invite", ), - re_path( - r"^data-export/notifications/google-cloud/$", - DataExportNotificationsEndpoint.as_view(), - name="sentry-api-0-data-export-notifications", - ), re_path( r"^notification-defaults/$", NotificationDefaultsEndpoints.as_view(), diff --git a/src/sentry/replays/endpoints/data_export_notifications.py b/src/sentry/replays/endpoints/data_export_notifications.py deleted file mode 100644 index 89d1bb36402a2d..00000000000000 --- a/src/sentry/replays/endpoints/data_export_notifications.py +++ /dev/null @@ -1,25 +0,0 @@ -import logging - -from rest_framework.request import Request -from rest_framework.response import Response - -from sentry.api.api_owners import ApiOwner -from sentry.api.api_publish_status import ApiPublishStatus -from sentry.api.base import Endpoint, control_silo_endpoint -from sentry.api.permissions import SentryIsAuthenticated -from sentry.replays.data_export import request_run_transfer_job, retry_transfer_job_run - -logger = logging.getLogger() - - -@control_silo_endpoint -class DataExportNotificationsEndpoint(Endpoint): - """PubSub notifications endpoint.""" - - owner = ApiOwner.DATA_BROWSING - publish_status = {"POST": ApiPublishStatus.PRIVATE} - permission_classes = (SentryIsAuthenticated,) - - def post(self, request: Request) -> Response: - retry_transfer_job_run(request.data, request_run_transfer_job) - return Response("", status=200) diff --git a/tests/sentry/replays/endpoints/test_data_export_notifications.py b/tests/sentry/replays/endpoints/test_data_export_notifications.py deleted file mode 100644 index d7b304855894a1..00000000000000 --- a/tests/sentry/replays/endpoints/test_data_export_notifications.py +++ /dev/null @@ -1,35 +0,0 @@ -import base64 -from unittest.mock import patch - -from sentry.testutils.cases import APITestCase -from sentry.testutils.silo import control_silo_test -from sentry.utils import json - - -@control_silo_test -class DataExportNotificationsTestCase(APITestCase): - endpoint = "sentry-api-0-data-export-notifications" - - def setUp(self) -> None: - super().setUp() - self.login_as(user=self.user) - - @patch("sentry.replays.endpoints.data_export_notifications.retry_transfer_job_run") - def test_simple(self, retry_transfer_job_run) -> None: # type: ignore[no-untyped-def] - retry_transfer_job_run.return_value = None - - data = { - "data": base64.b64encode( - json.dumps( - { - "transferOperation": { - "status": "FAILED", - "transferJobName": "test", - "projectId": "test-project", - } - } - ).encode() - ).decode("utf-8") - } - self.get_success_response(method="post", **data, status_code=200) - assert retry_transfer_job_run.called From dc42d7dba1f3da34da61bc01ac3e3e07ee2fb9ff Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 20:29:24 +0000 Subject: [PATCH 2/2] :hammer_and_wrench: Sync API Urls to TypeScript --- static/app/utils/api/knownSentryApiUrls.generated.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/static/app/utils/api/knownSentryApiUrls.generated.ts b/static/app/utils/api/knownSentryApiUrls.generated.ts index df57f57867638a..f80f30e39d2d37 100644 --- a/static/app/utils/api/knownSentryApiUrls.generated.ts +++ b/static/app/utils/api/knownSentryApiUrls.generated.ts @@ -30,7 +30,6 @@ export type KnownSentryApiUrls = | '/authenticators/' | '/broadcasts/' | '/broadcasts/$broadcastId/' - | '/data-export/notifications/google-cloud/' | '/doc-integrations/' | '/doc-integrations/$docIntegrationIdOrSlug/' | '/doc-integrations/$docIntegrationIdOrSlug/avatar/'