Skip to content

Commit 4e3e916

Browse files
committed
ref: Gate SentryApp actor resolution on is_sentry_app
Gate the app lookup on request.user.is_sentry_app rather than application_id alone. An OAuth client acting on behalf of a user (e.g. the MCP) also carries an application_id but authenticates as the real user, so it must resolve to USER -- and gating on application_id would run an app lookup (uncached on a None result) on every such request. is_sentry_app is true only for an app's proxy user, so only genuine app-as-itself tokens pay the cached lookup.
1 parent fff0cb0 commit 4e3e916

2 files changed

Lines changed: 12 additions & 3 deletions

File tree

src/sentry/issues/action_log/base.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,11 @@ def resolve_action_actor(request: Request) -> GroupActionActor:
125125
if organization_id is not None:
126126
return GroupActionActor.org(organization_id)
127127
elif kind == "api_token":
128-
# A token bound to an ApiApplication belongs to an integration; resolve the owning
129-
# SentryApp (control silo) so the actor is the app, not its proxy user.
128+
user = getattr(request, "user", None)
130129
application_id = getattr(auth, "application_id", None)
131-
if application_id is not None:
130+
# Gate on is_sentry_app (the app's proxy user), not application_id: an OAuth client
131+
# acting for a user (e.g. the MCP) also has an application_id but stays USER.
132+
if getattr(user, "is_sentry_app", False) and application_id is not None:
132133
from sentry.sentry_apps.services.app import app_service
133134

134135
sentry_app = app_service.get_by_application_id(application_id=application_id)

tests/sentry/issues/test_action_log.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ def test_sentry_app_token_resolves_to_app(self) -> None:
175175
request = self._request(auth=auth, user=sentry_app.proxy_user)
176176
assert resolve_action_actor(request) == GroupActionActor.sentry_app(sentry_app.id)
177177

178+
def test_user_oauth_token_with_application_id_is_user(self) -> None:
179+
# An OAuth client acting on behalf of a user (e.g. the MCP) has an application_id but
180+
# authenticates as the real user (is_sentry_app=False), so it must resolve to USER and
181+
# not trigger a SentryApp lookup.
182+
auth = AuthenticatedToken(kind="api_token", user_id=self.user.id, application_id=987654)
183+
request = self._request(auth=auth, user=self.user)
184+
assert resolve_action_actor(request) == GroupActionActor.user(self.user.id)
185+
178186
def test_unauthenticated_is_system(self) -> None:
179187
assert resolve_action_actor(self._request()) == SYSTEM_ACTOR
180188

0 commit comments

Comments
 (0)