Skip to content

Conversation

@everettbu
Copy link
Contributor

@everettbu everettbu commented Jul 26, 2025

Test 4

…#67876)

We're adding one more step in the GitHub integration installation
pipeline, namely GitHub OAuth2 authorize. This is transparent from the
UX perspective as the data exchange happens without user interaction.

The pipeline will now fail in these cases:
- If there is a mismatch between currently authenticated GitHub user
(derived from OAuth2 authorize step) and the user who installed the
GitHub app (https://github.com/apps/sentry-io)
- If there is a mismatch between `state` parameter supplied by user and
pipeline signature
- If GitHub could not generate correct `access_token` from the `code`
(wrong or attempt of re-use of `code`).

In all those cases, this error is shown:

![image](https://github.com/getsentry/sentry/assets/1127549/18923861-2ead-4cf5-adda-7738aef801d7)
Copilot AI review requested due to automatic review settings July 26, 2025 19:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR enhances the security of GitHub OAuth integration by implementing user authentication verification to prevent unauthorized installation hijacking.

  • Adds OAuth login step to verify the GitHub user's identity before proceeding with integration setup
  • Implements user mismatch validation to ensure the authenticated user matches the app installer
  • Refactors error handling with a unified error response function

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
tests/sentry/integrations/github/test_integration.py Adds comprehensive test coverage for OAuth flow, user mismatch scenarios, and updates existing tests to handle the new authentication step
src/sentry/web/frontend/pipeline_advancer.py Removes the global FORWARD_INSTALL_FOR constant and inlines the GitHub-specific logic for better maintainability
src/sentry/integrations/github/integration.py Implements new OAuthLoginView for user authentication, adds user validation in GitHubInstallation, and introduces unified error handling

)

auth_header = responses.calls[0].request.headers["Authorization"]
auth_header = responses.calls[2].request.headers["Authorization"]
Copy link

Copilot AI Jul 26, 2025

Choose a reason for hiding this comment

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

The hardcoded index responses.calls[2] creates a brittle test that depends on the exact number and order of HTTP calls. Consider using a more robust approach like filtering calls by URL or method, or using a helper method to find the specific call you need to verify.

Suggested change
auth_header = responses.calls[2].request.headers["Authorization"]
auth_call = next(
call for call in responses.calls if "Authorization" in call.request.headers
)
auth_header = auth_call.request.headers["Authorization"]

Copilot uses AI. Check for mistakes.
)

# At this point, we are past the GitHub "authorize" step
if request.GET.get("state") != pipeline.signature:
Copy link

Copilot AI Jul 26, 2025

Choose a reason for hiding this comment

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

The state parameter comparison should use a constant-time comparison to prevent timing attacks. Consider using secrets.compare_digest() or Django's constant_time_compare() function instead of the != operator.

Suggested change
if request.GET.get("state") != pipeline.signature:
from django.utils.crypto import constant_time_compare
if not constant_time_compare(request.GET.get("state"), pipeline.signature):

Copilot uses AI. Check for mistakes.
Comment on lines +428 to +429
except Exception:
payload = {}
Copy link

Copilot AI Jul 26, 2025

Choose a reason for hiding this comment

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

Catching all exceptions with a bare Exception clause can mask important errors and make debugging difficult. Consider catching specific exceptions like UnicodeDecodeError, ValueError, or other expected exceptions that could occur during response parsing.

Suggested change
except Exception:
payload = {}
except UnicodeDecodeError as e:
logger.error("Failed to decode response body: %s", e)
payload = {}
except ValueError as e:
logger.error("Failed to parse response body into a dictionary: %s", e)
payload = {}

Copilot uses AI. Check for mistakes.
Comment on lines +501 to +503
if (
pipeline.fetch_state("github_authenticated_user")
!= integration.metadata["sender"]["login"]
Copy link

Copilot AI Jul 26, 2025

Choose a reason for hiding this comment

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

The user login comparison should use a constant-time comparison to prevent timing attacks that could leak information about valid usernames. Consider using secrets.compare_digest() or Django's constant_time_compare() function.

Suggested change
if (
pipeline.fetch_state("github_authenticated_user")
!= integration.metadata["sender"]["login"]
from secrets import compare_digest
if not compare_digest(
str(pipeline.fetch_state("github_authenticated_user")),
str(integration.metadata["sender"]["login"]),

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants