Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/sentry/identity/vercel/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class VercelIdentityProvider(OAuth2Provider):
key = "vercel"
name = "Vercel"

oauth_authorize_url = "https://vercel.com/oauth/authorize"
# https://vercel.com/docs/integrations/reference#using-the-vercel-api/exchange-code-for-access-token
oauth_access_token_url = "https://api.vercel.com/v2/oauth/access_token"

Expand Down
19 changes: 17 additions & 2 deletions src/sentry/integrations/vercel/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from sentry import options
from sentry.constants import ObjectStatus
from sentry.identity.pipeline import IdentityPipeline
from sentry.identity.vercel.provider import VercelIdentityProvider
from sentry.integrations.base import (
FeatureDescription,
IntegrationData,
Expand All @@ -24,7 +25,7 @@
from sentry.integrations.pipeline import IntegrationPipeline
from sentry.integrations.services.integration import integration_service
from sentry.organizations.services.organization.model import RpcOrganization
from sentry.pipeline.views.base import PipelineView
from sentry.pipeline.views.base import ApiPipelineSteps, PipelineView
from sentry.pipeline.views.nested import NestedPipelineView
from sentry.projects.services.project.model import RpcProject
from sentry.projects.services.project_key import project_key_service
Expand Down Expand Up @@ -435,8 +436,22 @@
def get_pipeline_views(self) -> Sequence[PipelineView[IntegrationPipeline]]:
return [self._identity_pipeline_view()]

def get_pipeline_api_steps(self) -> ApiPipelineSteps[IntegrationPipeline]:
provider = VercelIdentityProvider(
redirect_url=absolute_uri(self.oauth_redirect_url),
)
return [
provider.make_oauth_api_step(bind_key="oauth_data"),
]

Check failure on line 445 in src/sentry/integrations/vercel/integration.py

View check run for this annotation

@sentry/warden / warden: sentry-backend-bugs

API pipeline flow will raise KeyError for missing state["user_id"]

The new `get_pipeline_api_steps()` method enables an API-driven pipeline flow for Vercel integration setup. However, when this flow completes and calls `build_integration()`, it accesses `state["user_id"]` (line 478) which is never bound to state in the API pipeline path. The legacy flow binds `user_id` via `integration_extension_configuration.py:130`, but `OrganizationPipelineEndpoint._initialize_pipeline()` does not bind `user_id` to state. Once `can_add` is set to `True`, this will cause a `KeyError` crash during integration setup.

def build_integration(self, state: Mapping[str, Any]) -> IntegrationData:
data = state["identity"]["data"]
# TODO: legacy views write token data to state["identity"]["data"] via
# NestedPipelineView. API steps write directly to state["oauth_data"].
# Remove the legacy path once the old views are retired.
if "oauth_data" in state:
data = state["oauth_data"]
else:
data = state["identity"]["data"]
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.

Missing user_id fallback causes KeyError in API flow

Medium Severity

In the API pipeline flow, state["user_id"] on line 478 will raise a KeyError. The legacy flow binds "user_id" to state data via pipeline.bind_state("user_id", request.user.id) in IntegrationExtensionConfigurationView.init_pipeline(), but the API flow never does this — only "oauth_data" is bound by the OAuth2ApiStep. The PR description says the code "falls back to state["uid"] for user_id in the API flow," but this fallback was not actually implemented.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 070a285. Configure here.

access_token = data["access_token"]
team_id = data.get("team_id")
client = VercelClient(access_token, team_id)
Comment on lines +451 to 457
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.

Bug: In the API pipeline path, state["user_id"] is never set, causing a KeyError in build_integration().
Severity: HIGH

Suggested Fix

In build_integration(), fall back to the request user when state["user_id"] is absent in the API flow. The pipeline's request.user.id is accessible via self.pipeline.request.user.id. Alternatively, bind user_id explicitly in get_pipeline_api_steps() or handle it in the api_finish_pipeline() call by injecting the user ID into state before calling build_integration.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/sentry/integrations/vercel/integration.py#L451-L457

Potential issue: In `build_integration()`, line 478 unconditionally accesses
`state["user_id"]` for `post_install_data`. In the legacy flow, the pipeline state has
`user_id` bound via `pipeline.bind_state("user_id", self.user.id)` at the Vercel
configure endpoint. However, in the new API pipeline flow (`get_pipeline_api_steps()`),
only `oauth_data` is bound to pipeline state by `OAuth2ApiStep.handle_post()`. The
authenticated user's ID is never written to `state["user_id"]` in the API flow. When
`api_finish_pipeline()` calls `build_integration(self.state.data)`, the resulting
`KeyError` on `state["user_id"]` will crash the integration installation. The PR
description mentions a `state["uid"]` fallback, but no such fallback is present in the
code.

Did we get this right? 👍 / 👎 to inform future reviews.

Expand Down
Loading