Skip to content

GitHub OAuth Security Enhancement#8

Open
rupakInfinitiBit wants to merge 2 commits into
oauth-state-vulnerablefrom
oauth-state-secure
Open

GitHub OAuth Security Enhancement#8
rupakInfinitiBit wants to merge 2 commits into
oauth-state-vulnerablefrom
oauth-state-secure

Conversation

@rupakInfinitiBit

Copy link
Copy Markdown
Owner

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.

…#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)
@graphbit-prflow-dev

Copy link
Copy Markdown

@rupakInfinitiBit 👋 I've started reviewing this pull request. I'll post a detailed review once I'm done — this may take a moment.

@pr-review-agent-rupakinfinitibit

Copy link
Copy Markdown

🔍 Reviewing PR #8

fix(security): validate GitHub user during integration installation
oauth-state-secureoauth-state-vulnerable  ·  3 file(s)  ·  @rupakInfinitiBit

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.

⏳ Analyzing changes — review incoming shortly.

@graphbit-prflow-dev graphbit-prflow-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Automated PR Review

PR Title: fix(security): validate GitHub user during integration installation
Overall Quality Score: 7/10
Recommendation: REQUEST_CHANGES


🧠 Summary

The PR improves security by validating GitHub user authentication during integration installation, but contains critical metadata validation gaps and security vulnerabilities that need addressing before approval.


⚠️ Critical Issues

  • Missing validation for pipeline state consistency - integration.metadata may not contain required 'sender' key before access
  • User authentication validation logic has potential for bypass if sender metadata is missing or corrupted
  • OAuth flow and user mismatch testing lack coverage for critical edge cases including missing sender metadata scenarios

✅ Key Suggestions

  • Replace simple string comparison with hmac.compare_digest() for state parameter validation to prevent timing attacks
  • Add comprehensive validation for integration.metadata structure before accessing nested keys
  • Implement specific exception handling instead of bare except Exception: clause
  • Add OAuth access token format validation to ensure tokens follow expected GitHub patterns
  • Expand test coverage to include failure scenarios, edge cases, and security attack vectors

return error(request, self.active_organization)

# Check that the authenticated GitHub user is the same as who installed the app.
if (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

HIGH: Missing validation for pipeline state consistency


        # 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"]

Suggested change:

+        if 'sender' not in integration.metadata or 'login' not in integration.metadata['sender']: return error(request, self.active_organization)

Add validation to ensure integration.metadata contains 'sender' key before accessing: if 'sender' not in integration.metadata or 'login' not in integration.metadata['sender']: return error(request, self.active_organization)

external_id=installation_id, status=ObjectStatus.ACTIVE
)
except Integration.DoesNotExist:
return error(request, self.active_organization)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

HIGH: User authentication validation logic has potential for bypass if sender metadata is missing

            )
        except Integration.DoesNotExist:
            return error(request, self.active_organization)

        # Check that the authenticated GitHub user is the same as who installed the app.

Suggested change:

+        if not integration.metadata.get('sender') or not integration.metadata['sender'].get('login'): return error(request, self.active_organization, error_short="Invalid installation metadata.", error_long="Installation metadata is corrupted or missing.")

Add comprehensive validation: if not integration.metadata.get('sender') or not integration.metadata['sender'].get('login'): return error(request, self.active_organization, error_short="Invalid installation metadata.", error_long="Installation metadata is corrupted or missing.")

@@ -218,14 +229,32 @@ def assert_setup_flow(self):
redirect = urlparse(resp["Location"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

HIGH: OAuth flow testing only covers happy path scenarios

Suggestion: Add test cases for OAuth failures: invalid codes, network errors, malformed responses, and state parameter mismatches

assert b"Invalid installation request." in resp.content

@responses.activate
def test_github_user_mismatch(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

HIGH: User mismatch test doesn't cover edge cases like missing sender metadata

Suggested change:

+        def test_github_user_mismatch_missing_sender_metadata():

Add test case: def test_github_user_mismatch_missing_sender_metadata(): to test when integration.metadata['sender'] is None or missing 'login' key

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔧 Changes Requested

████████░░ 7.5/10  ·  3 files reviewed  ·  7 issues found

Key concerns:

  • Overly broad exception handling in OAuth token exchange could mask critical errors
  • Unsafe access to integration metadata without proper validation could cause KeyError
  • Missing validation for installation_id parameter format and bounds
  • Test coverage could be more comprehensive for attack scenarios
📝 Walkthrough (3 files)
File Changes Summary
src/sentry/integrations/github/integration.py ✏️ +118/-41 Adds GitHub OAuth2 user validation step before integration installation and refactors error handling to use common helper functions.
src/sentry/web/frontend/pipeline_advancer.py ✏️ +4/-7 Simplified GitHub-specific installation logic by removing the FORWARD_INSTALL_FOR constant and inlining the check.
tests/sentry/integrations/github/test_integration.py ✏️ +125/-2 Updated tests to cover the new OAuth authentication flow and user validation, including comprehensive test cases for user mismatch scenarios.

🔍 Issues

🟠 src/sentry/integrations/github/integration.py

  • 🟠 Line 442Overly broad exception handling masks important errors

    The bare except Exception: clause catches all exceptions during token exchange, including network errors, JSON parsing errors, and other important failures. This makes debugging OAuth issues extremely difficult and could hide security-relevant errors.

try:
    body = safe_urlread(req).decode("utf-8")
    payload = dict(parse_qsl(body))
except (UnicodeDecodeError, ValueError, OSError) as e:
    logger.warning("GitHub OAuth token exchange failed: %s", e)
    payload = {}
  • 🟡 Line 500Unsafe access to nested metadata without validation

    Direct access to integration.metadata["sender"]["login"] could raise KeyError if the metadata structure is missing or malformed. This could happen if the integration was created through a different flow or if GitHub's webhook payload structure changes.

# Check that the authenticated GitHub user is the same as who installed the app.
authentic_user = pipeline.fetch_state("github_authenticated_user")
installer_login = integration.metadata.get("sender", {}).get("login")
if not installer_login:
    logger.warning("Integration %s missing sender metadata", integration.id)
    return error(request, self.active_organization)
if authentic_user != installer_login:
    return error(request, self.active_organization)
  • 🔵 Line 447Missing validation for installation_id parameter

    The installation_id from the request is not validated for format or reasonable bounds before being used in database queries and external API calls.

installation_id = request.GET.get(
    "installation_id", pipeline.fetch_state("installation_id")
)
if installation_id is None:
    return self.redirect(self.get_app_url())

# Validate installation_id format
if not installation_id.isdigit() or len(installation_id) > 20:
    return error(request, self.active_organization)

pipeline.bind_state("installation_id", installation_id)
  • 🔵 Line 446Inconsistent error handling between OAuth and installation views

    The OAuthLoginView and GitHubInstallation views handle errors differently - OAuth view calls error() helper while installation view previously had inline error handling. Consider standardizing the approach.

🔵 src/sentry/web/frontend/pipeline_advancer.py

  • 🔵 Line 40Hardcoded provider check could be more maintainable

    The hardcoded check for 'github' could be made more maintainable by using a provider-based approach or constant, especially if other integrations need similar behavior.

if (
    provider_id == GitHubIntegrationProvider.key
    and request.GET.get("setup_action") == "install"
    and pipeline is None
):

🟡 tests/sentry/integrations/github/test_integration.py

  • ℹ️ Line 118Good test coverage for OAuth flow

    The test updates properly cover the new OAuth flow by stubbing the necessary GitHub OAuth endpoints and validating the complete flow.

  • 🟡 Line 399User mismatch test could be more comprehensive

    The test_github_user_mismatch test creates a webhook event with an 'attacker' user but doesn't verify that the original authenticated user differs from the webhook sender. Consider making the test more explicit about the attack scenario.

    💡 Suggestion: # Before creating webhook with attacker, verify the original authenticated user
    original_integration = Integration.objects.get(external_id=self.installation_id)
    original_sender = original_integration.metadata.get('sender', {}).get('login', 'octocat')
    assert original_sender != 'attacker', 'Test setup should use different users'

webhook_event = json.loads(INSTALLATION_EVENT_EXAMPLE)

✨ Strengths

  • ✅ Addresses important security vulnerability by validating GitHub user identity
  • ✅ Clean refactoring of error handling into reusable helper functions
  • ✅ Comprehensive test coverage for the new OAuth flow and edge cases

🤖 PRFlow  ·  🔴 Critical  🟠 Important  🟡 Suggestion  🔵 Minor  ℹ️ Note

Comment thread src/sentry/integrations/github/integration.py Outdated
except Integration.DoesNotExist:
return error(request, self.active_organization)

# Check that the authenticated GitHub user is the same as who installed the app.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔧 Improvement | 🟡 Suggestion

Unsafe access to nested metadata without validation

Direct access to integration.metadata["sender"]["login"] could raise KeyError if the metadata structure is missing or malformed. This could happen if the integration was created through a different flow or if GitHub's webhook payload structure changes.

Suggested change
# Check that the authenticated GitHub user is the same as who installed the app.
# Check that the authenticated GitHub user is the same as who installed the app.
authentic_user = pipeline.fetch_state("github_authenticated_user")
installer_login = integration.metadata.get("sender", {}).get("login")
if not installer_login:
logger.warning("Integration %s missing sender metadata", integration.id)
return error(request, self.active_organization)
if authentic_user != installer_login:
return error(request, self.active_organization)
🤖 Prompt for AI Agents
In `@src/sentry/integrations/github/integration.py` around line 500, Direct access to `integration.metadata["sender"]["login"]` could raise KeyError if the metadata structure is missing or malformed. This could happen if the integration was created through a different flow or if GitHub's webhook payload structure changes.; apply the proposed fix to resolve this validation.

assert b"Invalid installation request." in resp.content

@responses.activate
def test_github_user_mismatch(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔧 Improvement | 🟡 Suggestion

User mismatch test could be more comprehensive

The test_github_user_mismatch test creates a webhook event with an 'attacker' user but doesn't verify that the original authenticated user differs from the webhook sender. Consider making the test more explicit about the attack scenario.

💡 Suggestion: # Before creating webhook with attacker, verify the original authenticated user
original_integration = Integration.objects.get(external_id=self.installation_id)
original_sender = original_integration.metadata.get('sender', {}).get('login', 'octocat')
assert original_sender != 'attacker', 'Test setup should use different users'

webhook_event = json.loads(INSTALLATION_EVENT_EXAMPLE)

🤖 Prompt for AI Agents
In `@tests/sentry/integrations/github/test_integration.py` around line 399, The test_github_user_mismatch test creates a webhook event with an 'attacker' user but doesn't verify that the original authenticated user differs from the webhook sender. Consider making the test more explicit about the attack scenario.; apply the proposed fix to resolve this testing.

@rupakInfinitiBit rupakInfinitiBit changed the title fix(security): validate GitHub user during integration installation GitHub OAuth Security Enhancement Mar 13, 2026
Co-authored-by: pr-review-agent-rupakinfinitibit[bot] <240878464+pr-review-agent-rupakinfinitibit[bot]@users.noreply.github.com>
@graphbit-prflow-dev

Copy link
Copy Markdown

@rupakInfinitiBit 👋 I've started reviewing this pull request. I'll post a detailed review once I'm done — this may take a moment.

@graphbit-prflow-dev graphbit-prflow-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Automated PR Review

PR Title: GitHub OAuth Security Enhancement
Overall Quality Score: 6/10
Recommendation: REQUEST_CHANGES


🧠 Summary

The code contains multiple structural issues including malformed class definitions and incorrect indentation that prevent proper execution, alongside some security improvements for OAuth token handling.


⚠️ Critical Issues

  • Syntax error with malformed class definition structure and orphaned try-except block at line 442
  • Missing class declaration for GitHubInstallation at line 450 - needs proper class definition: class GitHubInstallation(PipelineView):
  • Method definition get_app_url exists outside of class scope at line 450 and must be moved inside the GitHubInstallation class with correct indentation

✅ Key Suggestions

  • Fix the structural issues by properly defining the GitHubInstallation class and moving methods into appropriate class scope
  • Improve error logging security by removing sensitive details from OAuth token exchange failure logs
  • Add validation for GitHub authenticated user in pipeline state to prevent null/empty comparisons
  • Replace broad exception handling with more specific exception types for better error handling

return pipeline.next_step()


try:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Syntax error - malformed class definition structure with orphaned try-except block

Suggestion: The try-except block appears to be incorrectly placed. It should either be part of the OAuthLoginView class or moved to the appropriate location within GitHubInstallation class

payload = {}
def get_app_url(self) -> str:
name = options.get("github-app.name")
return f"https://github.com/apps/{slugify(name)}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Indentation inconsistency and missing class declaration for GitHubInstallation

Suggested change:

+        class GitHubInstallation(PipelineView):

Add proper class declaration: class GitHubInstallation(PipelineView):

@rupakInfinitiBit rupakInfinitiBit marked this pull request as draft April 28, 2026 07:04
@rupakInfinitiBit rupakInfinitiBit marked this pull request as ready for review April 28, 2026 07:04
@rupakInfinitiBit rupakInfinitiBit marked this pull request as draft April 28, 2026 07:58
@rupakInfinitiBit rupakInfinitiBit marked this pull request as ready for review April 28, 2026 07:58
@graphbit-prflow

Copy link
Copy Markdown

🔍 Reviewing PR #8

GitHub OAuth Security Enhancement
oauth-state-secureoauth-state-vulnerable  ·  3 file(s)  ·  @rupakInfinitiBit

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.

⏳ Analyzing changes — review incoming shortly.

@graphbit-prflow graphbit-prflow Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💬 Review Complete

███████░░░ 7.0/10  ·  0 files reviewed  ·  0 issues found

Key concerns:

  • Automated review failed: Single '}' encountered in format string
📝 Walkthrough (3 files)
File Changes Summary
src/sentry/integrations/github/integration.py ✏️ +124/-42 Review unavailable due to service error.
src/sentry/web/frontend/pipeline_advancer.py ✏️ +4/-7 Review unavailable due to service error.
tests/sentry/integrations/github/test_integration.py ✏️ +125/-2 Review unavailable due to service error.

✅ No Issues Found

Looks good — no actionable issues detected.


🤖 PRFlow  ·  🔴 Critical  🟠 Important  🟡 Suggestion  🔵 Minor  ℹ️ Note

Repository owner deleted a comment from coderabbitai Bot Apr 28, 2026
Repository owner deleted a comment from graphbit-prflow Bot Apr 28, 2026
Repository owner deleted a comment from graphbit-prflow Bot Apr 28, 2026
@rupakInfinitiBit rupakInfinitiBit marked this pull request as draft April 28, 2026 11:34
@rupakInfinitiBit rupakInfinitiBit marked this pull request as ready for review April 28, 2026 11:34
@graphbit-prflow

Copy link
Copy Markdown

🔍 Reviewing PR #8

GitHub OAuth Security Enhancement
oauth-state-secureoauth-state-vulnerable  ·  3 file(s)  ·  @rupakInfinitiBit

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.

⏳ Analyzing changes — review incoming shortly.

@graphbit-prflow graphbit-prflow Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💬 Review Complete

███████░░░ 7.0/10  ·  0 files reviewed  ·  0 issues found

Key concerns:

  • Automated review failed: Single '}' encountered in format string
📝 Walkthrough (3 files)
File Changes Summary
src/sentry/integrations/github/integration.py ✏️ +124/-42 Review unavailable due to service error.
src/sentry/web/frontend/pipeline_advancer.py ✏️ +4/-7 Review unavailable due to service error.
tests/sentry/integrations/github/test_integration.py ✏️ +125/-2 Review unavailable due to service error.

✅ No Issues Found

Looks good — no actionable issues detected.


🤖 PRFlow  ·  🔴 Critical  🟠 Important  🟡 Suggestion  🔵 Minor  ℹ️ Note

Repository owner deleted a comment from coderabbitai Bot Apr 28, 2026
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