Skip to content

fix(vercel): tighten internal integration scopes to org:ci#113394

Open
dcramer wants to merge 2 commits intomasterfrom
cursor/vercel-tighten-token-scopes-6638
Open

fix(vercel): tighten internal integration scopes to org:ci#113394
dcramer wants to merge 2 commits intomasterfrom
cursor/vercel-tighten-token-scopes-6638

Conversation

@dcramer
Copy link
Copy Markdown
Member

@dcramer dcramer commented Apr 19, 2026

Reduces the scopes on the internal Sentry app provisioned by the Vercel integration to the absolute minimum needed.

What changes

  • src/sentry/integrations/vercel/integration.py: replaces the internal integration scopes ["project:releases", "project:read", "project:write"] with ["org:ci"].

Why

The Vercel integration creates an internal Sentry app (Vercel Internal Integration) and stores the resulting auth token in Vercel as an environment variable so that Vercel-side build/deploy hooks can call back into Sentry. Tightening these scopes shrinks the blast radius if a Vercel-stored token is leaked.

The only token-authenticated outbound call from the Vercel integration is the deployment webhook posting to POST /api/0/organizations/{org}/releases/, which is guarded by OrganizationReleasePermission. That permission already accepts org:ci:

  • src/sentry/api/bases/organization.py -> OrganizationReleasePermission allows org:ci on GET/POST/PUT.
  • src/sentry/integrations/vercel/webhook.py -> only request that uses the token is POST {org}/releases/.

The other places the integration touches project/key data (project list, default DSN/project key, log-drain URL, OTLP URL) all happen via internal services in-process during the Vercel project wiring flow, not through token-authenticated public API endpoints. Those reads do not consume token scopes, so dropping project:read and project:write from the token does not affect them.

Impact

  • Existing tokens already issued for installed Vercel integrations are not changed by this diff. Only newly-created Vercel internal integrations will get the tightened scope set.
  • Follow-ups in the linked issue cover marking the auth token as a sensitive value and provisioning separate tokens per Vercel environment.

Test plan

Ran the full Vercel integration test suite locally against this change:

.venv/bin/pytest --reuse-db tests/sentry/integrations/vercel/
...
45 passed in 24.42s

This includes test_webhook.py, which exercises the release-creation flow that actually consumes the internal token.

Refs #113391

The Vercel integration creates an internal Sentry app and uses its token
to create releases via the webhook. The only token-authenticated outbound
call is to POST /api/0/organizations/{org}/releases/, which already
accepts org:ci.

The existing project:read and project:write scopes were unused by any
token-authenticated path. Project/DSN/key lookups during the wiring flow
all happen via internal services in-process and do not consume external
token scopes.

Reducing to org:ci minimizes blast radius if a Vercel-stored token is
compromised.

Refs #113391

Co-authored-by: David Cramer <dcramer@users.noreply.github.com>
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 19, 2026
@dcramer dcramer marked this pull request as ready for review April 19, 2026 18:55
@dcramer dcramer requested review from a team as code owners April 19, 2026 18:55
sentry[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

Copy link
Copy Markdown
Member

@leeandher leeandher left a comment

Choose a reason for hiding this comment

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

makes sense, don't see issues here aside from the UI bit

)
assert response.status_code == 201, response.content
assert Release.objects.filter(organization_id=org.id, version="1.2.1").exists()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this portion of the test doesn't seem necessary

verify_install=False,
overview=internal_integration_overview.strip(),
scopes=["project:releases", "project:read", "project:write"],
scopes=["org:ci"],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the internal integrations can always have their perms raised by the orgs themselves if they go to the edit page. this is how they currently appear after install:

Image

We don't surface org:ci as an option here, so I think people who go to manage these new installs will see form errors if they try an edit the apps now

const matches = message.match(/Requested permission of (\w+:\w+)/);
if (matches) {
const scope = matches[1];
const resource = getResourceFromScope(scope as Scope);
// should always match but technically resource can be undefined
if (resource) {
formErrors[`${resource}--permission`] = [message];
}

If there's no issue in publicizing it, we can add org:ci to SENTRY_APP_PERMISSIONS on the frontend to avoid this

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.

4 participants