-
Notifications
You must be signed in to change notification settings - Fork 0
GitHub OAuth Security Enhancement #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: oauth-state-vulnerable
Are you sure you want to change the base?
Conversation
…#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: 
|
@claude review this PR |
|
Claude finished @akshayutture-augment's task —— View job Code Review: GitHub OAuth Security EnhancementSummaryThis PR introduces a critical security vulnerability by creating an authentication bypass through a race condition in the user validation logic. While the PR adds an OAuth flow to verify user identity during GitHub integration installation, the implementation has a fundamental flaw that could allow attackers to hijack GitHub integrations. Critical Security Issues🔴 CRITICAL: Authentication Bypass Through Race ConditionLocation: The new code validates that the authenticated user matches the installation user: # Check that the authenticated GitHub user is the same as who installed the app.
if (
pipeline.fetch_state("github_authenticated_user")
!= integration.metadata["sender"]["login"]
):
return error(request, self.active_organization)VULNERABILITY: This check relies on Attack Scenario:
Root Cause: The code at lines 477-482 only checks if an OrganizationIntegration exists, not whether the Integration is already "claimed": try:
# We want to limit GitHub integrations to 1 organization
installations_exist = OrganizationIntegration.objects.filter(
integration=Integration.objects.get(external_id=installation_id)
).exists()
except Integration.DoesNotExist:
return pipeline.next_step() # New integration, proceed to build_integration
if installations_exist:
return error(...) # Already linked to an org
# BUG: If Integration exists but no OrganizationIntegration, code continues
# This allows attacker who created Integration to link it to any orgFIX: The validation should happen BEFORE the Integration is created, or you need to verify the authenticated user matches the sender at the webhook reception time, not later. Consider: # Option 1: Check that Integration doesn't exist at all for new installations
try:
integration = Integration.objects.get(external_id=installation_id)
# Integration already exists - someone already installed this
# Only allow if they're linking to the SAME org that started the flow
if pipeline.fetch_state("github_authenticated_user") != integration.metadata.get("sender", {}).get("login"):
return error(request, self.active_organization)
# Check if already linked to an org
if OrganizationIntegration.objects.filter(integration=integration).exists():
return error(request, self.active_organization,
error_short="Github installed on another Sentry organization.",
error_long=ERR_INTEGRATION_EXISTS_ON_ANOTHER_ORG)
except Integration.DoesNotExist:
return pipeline.next_step()
# Option 2: Store a secure token during webhook reception and validate it here🟡 MODERATE: Missing Metadata ValidationLocation: != integration.metadata["sender"]["login"]ISSUE: No validation that
FIX: Add defensive checks: sender = integration.metadata.get("sender", {})
sender_login = sender.get("login") if isinstance(sender, dict) else None
if not sender_login:
logger.warning("Integration missing sender metadata", extra={"integration_id": integration.id})
return error(request, self.active_organization)
if pipeline.fetch_state("github_authenticated_user") != sender_login:
return error(request, self.active_organization)🟡 MODERATE: No Error Handling for get_user_infoLocation: authenticated_user_info = get_user_info(payload["access_token"])
if "login" not in authenticated_user_info:
return error(request, self.active_organization)ISSUE: The code doesn't handle exceptions or malformed responses from
FIX: Add proper error handling: try:
authenticated_user_info = get_user_info(payload["access_token"])
except Exception as e:
logger.exception("Failed to get GitHub user info", exc_info=e)
return error(request, self.active_organization)
if not authenticated_user_info or "login" not in authenticated_user_info:
logger.warning("Invalid user info response", extra={"response": authenticated_user_info})
return error(request, self.active_organization)🟡 MODERATE: Silent Exception HandlingLocation: try:
body = safe_urlread(req).decode("utf-8")
payload = dict(parse_qsl(body))
except Exception:
payload = {}ISSUE: Silently catches all exceptions and returns empty dict. This masks real errors like:
FIX: Add logging and be more specific: try:
body = safe_urlread(req).decode("utf-8")
payload = dict(parse_qsl(body))
except Exception as e:
logger.exception("Failed to exchange OAuth code for token", exc_info=e)
payload = {}🟢 MINOR: Inconsistent CapitalizationLocation: error_short="Github installed on another Sentry organization."Should be "GitHub" not "Github" for consistency. Positive Aspects✅ Good: Added OAuth flow to verify user identity - the approach is sound Test Coverage AnalysisStrong Coverage:
Missing Coverage:
Test Analysis - The test simulates an attacker webhook but doesn't fully test the vulnerability: webhook_event["sender"]["login"] = "attacker" # Sets sender to attacker
# ... webhook creates Integration with sender="attacker" ...
resp = self.client.get(init_path_2) # Different user initiates flow
resp = self.client.get(setup_path_2) # OAuth returns "octocat" as authenticated user
# Test expects this to FAIL because octocat != attackerThis test validates the security check works when users DON'T match. But it doesn't test when:
RecommendationsCRITICAL - Must Fix Before Merge:
Strongly Recommended:
Security Impact AssessmentCurrent Code: 🔴 HIGH RISK - The race condition could allow attackers to hijack GitHub integrations and gain unauthorized access to repository data, commit information, and potentially trigger deployments or access sensitive code. Recommendation: Do not merge without addressing the critical race condition vulnerability. |
Test 4
Replicated from ai-code-review-evaluation/sentry-greptile#4