Skip to content

fix(oauth): Request only skill-derived scopes from Sentry - #1546

Closed
sentry-junior[bot] wants to merge 4 commits into
mainfrom
test/oauth-upstream-scopes-match-skills
Closed

sentry-junior[bot] wants to merge 4 commits into
mainfrom
test/oauth-upstream-scopes-match-skills

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Requests only the Sentry scopes the selected skills need, instead of always requesting write access.

Background: why every scope was requested

redirectToUpstream (packages/mcp-cloudflare/src/server/oauth/routes/authorize.ts) always requested every scope in SCOPES, org:read project:write team:write event:write alerts:write, whatever skills were selected. This was intentional. docs/operations/security.md gave three reasons:

  1. Sentry OAuth scopes are coarse compared to the MCP skill model.
  2. One upstream token has to support every tool the granted skills could enable.
  3. The same upstream token is reused across MCP token refreshes and requests for that grant.

The actual permission boundary was the MCP-side skill gating, not the Sentry token. As a result, a user who approved only Inspect Issues & Events still saw Sentry's consent screen ask for read and write access to teams, projects, events, and alerts, and the server-side token had that access.

Was it necessary?

As far as I can tell, no. None of the three reasons requires write scopes for read-only skills:

  • Coarse scopes. We already map tools to scopes. Every tool declares requiredScopes, and getScopesForSkills() turns a set of skills into the scopes those tools need. It was only used after consent (callback.ts, stored as grantedScopes) and never sent to Sentry.
  • One token for every enabled tool. Skills are chosen at consent time and stored on the grant. Requesting getScopesForSkills(selected) gives a token that covers every tool those skills enable.
  • Reuse across refreshes. Refreshes keep the same grant and the same skills, so the token's scopes still match. Granting more skills later goes through the OAuth flow again, and Sentry asks the user to approve any new scopes.

The one real dependency was hidden: a tool whose declared requiredScopes was wrong. analyze_issue_with_seer declared [], but starting an autofix run is a POST /organizations/{org}/issues/{id}/autofix/, and Sentry gates that on event:write (GroupAiPermission). It only worked because the token always had event:write. This PR fixes that declaration.

Changes

  • authorize.ts: build the upstream scope from the approved skills with getScopesForSkills(parseSkills(skills).valid). Inspect-only now requests event:read org:read project:read team:read.
  • analyze-issue-with-seer.ts: requiredScopes: ["event:write"]. The generated toolDefinitions.json and skillDefinitions.json are regenerated.
  • docs/operations/security.md: describe skill-derived upstream scopes instead of a fixed set.
  • authorize.test.ts: assert the exact scope= sent to Sentry, reads included:
    • inspect gets exactly event:read org:read project:read team:read
    • inspect+seer adds event:write
    • inspect, +seer, +triage, +project-management each equal getScopesForSkills(selected)

Audit

I checked every tool that calls a write endpoint (POST, PUT, or DELETE) against the scopes Sentry requires for that endpoint in getsentry/sentry:

  • Triage (update issue, add note, link and unlink external issues): event:write, which the skill grants.
  • Manage Projects & Teams (projects, teams, DSNs, issue alerts, metric, cron, and uptime monitors): project:write, team:write, or alerts:write, which the skill grants.
  • Search agent and onboarding status POSTs: org:read.
  • Seer:
    • starting autofix needs event:write, which was missing and is fixed here
    • reading existing autofix state (get_issue_details, GET .../autofix/) needs event:read
    • the natural-language search agent needs org:read

Read-only tools are covered because the default read scopes (org:read project:read team:read event:read) are always requested.

Not in this PR

  • routes/chat-oauth.ts (the separate chat sign-in) still requests the full SCOPES set. It doesn't use skills.
  • Grants issued before this change keep their broad upstream token until the user authorizes again.

Testing

  • @sentry/mcp-cloudflare: 438/438 pass
  • @sentry/mcp-core: 1823 pass (6 skipped); validate-skills passes
  • oxfmt, oxlint, and tsc are clean
  • On main without the fix, the new scope tests fail. Every case requests alerts:write event:write org:read project:write team:write.
  • Not yet checked end to end against a live Sentry org.

via shaun.kaasten.

--

View Junior Session [Sentry]

The approval POST redirects to Sentry's /oauth/authorize/ with every scope
in SCOPES, regardless of which skills the user selected. Selecting only
'inspect' still requests project/team/event/alerts write scopes.

These tests compare the write scopes requested upstream against
getScopesForSkills() for the selected skills. They fail on main and pass
once redirectToUpstream requests skill-derived scopes.

Co-Authored-By: Shaun Kaasten <shaun.kaasten@sentry.io>
@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Oct 19, 2026 8:13pm UTC

Request Review

Check the full scope list sent to Sentry rather than only the absence of
extra write scopes. Inspect-only must request exactly event:read,
org:read, project:read, and team:read; other skill sets must request
exactly getScopesForSkills(selected).

Co-Authored-By: Shaun Kaasten <shaun.kaasten@sentry.io>
The approval POST redirected to Sentry's /oauth/authorize/ with every
scope in SCOPES, so the consent screen and the upstream token included
project/team/event/alerts write access even when only read-only skills
were selected.

Compute the upstream scope from the approved skills with
getScopesForSkills(). Inspect-only now requests
event:read org:read project:read team:read.

analyze_issue_with_seer declared no required scopes, but starting an
autofix run POSTs to /issues/{id}/autofix/, which Sentry gates on
event:write (GroupAiPermission). Declare event:write so the Seer skill
keeps working with the narrower upstream token.

Update docs/operations/security.md to describe skill-derived upstream
scopes.

Co-Authored-By: Shaun Kaasten <shaun.kaasten@sentry.io>
@sentry-junior sentry-junior Bot changed the title test(oauth): Assert upstream Sentry scopes match selected skills fix(oauth): Request only skill-derived scopes from Sentry Oct 19, 2026
@skaasten
skaasten marked this pull request as ready for review October 19, 2026 19:36
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Oct 19, 2026

@DominikB2014 DominikB2014 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect we still need some more scopes to make the tools actual useful, and i think we'll need to go through all these tools to make sure the scopes are correct. For example for search_traces: I believe a lot of the tracing actually requires org:read and not event:read https://docs.sentry.io/api/discover/retrieve-a-trace/, and https://docs.sentry.io/api/explore/query-explore-events-in-table-format/ are examples

Trace, trace-meta, events, events-timeseries, events/validate, and
trace-items/attributes all inherit OrganizationEventsEndpointBase, which
uses OrganizationPermission (GET requires org:read), not event:read.

- get_trace_details / get_span_details: org:read
- search_events and dataset search tools (searchToolBase): org:read +
  project:read (project lookup via ProjectDetailsEndpoint)

No change to requested OAuth scopes: DEFAULT_SCOPES already includes
org:read and project:read. This makes the per-tool declarations accurate.
@skaasten

Copy link
Copy Markdown
Contributor

Decided the benefits are not worth the maintenance effort to keep these scopes in sync with api calls.

@skaasten skaasten closed this Oct 20, 2026

This branch was previously deployed

2 inactive deployments
Actions — 43206825 Deployed Oct 19, 2026 by sentry-junior[bot] via eval #1329
Preview — 43206825 Deployed Oct 19, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants