Skip to content

ref(service-accounts): Remove the ServiceAccountId runtime guard - #129763

Open
gricha wants to merge 1 commit into
masterfrom
ref/sa-drop-service-account-id
Open

gricha wants to merge 1 commit into
masterfrom
ref/sa-drop-service-account-id

Conversation

@gricha

@gricha gricha commented Nov 3, 2026

Copy link
Copy Markdown
Member

A service account on request.user no longer has an id. Reading it raises AttributeError, so code that needs a person fails loudly instead of getting a stand-in id.

ServiceAccountId never equaled a user id and raised UserRequired when turned into an int. It covered code mypy didn't check while request.user was only partly typed. With #129640 typing request.user everywhere in sentry, checked code can't read .id without narrowing, so the guard only turned real bugs into 403s. The APIException passthrough in RPC argument serialization existed only for this guard and goes with it.

The 82 service-account test files (~3,900 tests) pass the same with and without the guard. The replay index, the one site that used to read a service account's id, already passes no actor for service accounts.

getsentry keeps the old request.user type, so a service account reaching request.user.id there now 500s (trial and plan-upgrade requests, Console SDK invites, docsupport). Those get fixed one at a time; billing writes are already refused by getsentry#22906.

@gricha
gricha requested a review from a team as a code owner November 3, 2026 20:22
@gricha
gricha added this pull request to stack #129747 November 3, 2026 20:22
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Nov 3, 2026
@gricha
gricha force-pushed the ref/sa-drop-service-account-id branch from bc4f222 to 126cbfc Compare November 3, 2026 20:51
@sentry

sentry Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Sentry Snapshot Testing

Name Added Removed Changed Renamed Unchanged Skipped Status
sentry-frontend
sentry-frontend
0 0 0 0 786 0 ✅ Unchanged

⚙️ sentry-frontend Snapshot Settings

@gricha
gricha force-pushed the ref/sa-drop-service-account-id branch 4 times, most recently from d0b527b to edc6cb4 Compare November 4, 2026 01:15
Base automatically changed from ref/drop-request-user-allowlist to master November 4, 2026 03:39
@gricha
gricha requested review from a team as code owners November 4, 2026 03:39
@gricha
gricha force-pushed the ref/sa-drop-service-account-id branch from edc6cb4 to 6dc283f Compare November 4, 2026 03:39
@gricha
gricha marked this pull request as draft November 4, 2026 18:04
gricha added a commit that referenced this pull request Nov 4, 2026
Service accounts can now read an event's grouping info. Callers with no
user skip the stacktrace-order preference and get the default order, the
same as org tokens.

`EventGroupingInfoEndpoint.get` typed its request as a plain
`HttpRequest`, so the `request.user` mypy check didn't cover its
`request.user.id` read. A service account got 403 `user-required` from
the `ServiceAccountId` guard. Once #129763 removes the guard, that
becomes an `AttributeError` (500). The request is now typed as a DRF
`Request` and reads `acting_user_id`.

The new test on master:

```
E   AssertionError: b'{"detail":{"code":"user-required","message":"This action requires a user.","extra":{}}}'
E   assert 403 == 200
FAILED tests/sentry/issues/endpoints/test_event_grouping_info.py::EventGroupingInfoEndpointTestCase::test_service_account
```

With the fix, the grouping info suite passes (7 tests), and the
`request.user.id` ratchet drops to 123.
gricha added a commit that referenced this pull request Nov 4, 2026
…9906)

`is_member_disabled_from_limit` now returns False for a caller with no
user, before any membership lookup. Seat limits apply to members, and a
service account isn't one.

The function takes an `HttpRequest`, so the `request.user` mypy check
didn't cover its `user.id` read. Sentry's own permission classes pass an
`RpcUserOrganizationContext` and never reach that read. getsentry's
customer details endpoint passes a DRF request with an `Organization`,
which goes to `check_membership_by_id(user_id=user.id)`. A service
account with `org:billing` gets 403 `user-required` there today, from
the `ServiceAccountId` guard. Once #129763 removes the guard, it gets a
500. Passing `acting_user_id` instead would send `None` into a lookup
that matches pending invites, so the function returns early.

The new test on master:

```
E   sentry.api.exceptions.UserRequired: {'detail': {'code': 'user-required', 'message': 'This action requires a user.', 'extra': {}}}
FAILED tests/sentry/api/test_utils.py::IsMemberDisabledFromLimitTest::test_service_account_is_not_a_limited_member
```

With the fix, the API utils, organization permission and permissions
suites pass (214 tests).
A service account on request.user no longer has an id. Reading it raises
AttributeError, so code that needs a person fails loudly instead of
getting a stand-in id.

ServiceAccountId never equaled a user id and raised UserRequired when
turned into an int. It covered code mypy didn't check while request.user
was only partly typed. Now that request.user is typed everywhere in
sentry, checked code can't read .id without narrowing, so the guard only
turned real bugs into 403s. The APIException passthrough in RPC argument
serialization existed only for this guard and is removed with it.
@gricha
gricha force-pushed the ref/sa-drop-service-account-id branch from 6dc283f to 0b94ac8 Compare November 5, 2026 23:14

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants