Skip to content

Fix GitHub App cross-org member enumeration using per-installation tokens#4774

Open
dustin-decker wants to merge 1 commit intomainfrom
fix/github-app-cross-org-member-enum
Open

Fix GitHub App cross-org member enumeration using per-installation tokens#4774
dustin-decker wants to merge 1 commit intomainfrom
fix/github-app-cross-org-member-enum

Conversation

@dustin-decker
Copy link
Contributor

@dustin-decker dustin-decker commented Feb 27, 2026

Summary

  • When a GitHub App is installed across multiple orgs, addMembersByApp discovers all installations via JWT but uses a single installation's token for ListMembers calls across all orgs
  • GitHub App installation tokens only bypass IP allowlists for their own org, so cross-org calls get 403s
  • Adds APIClientForInstallation to appConnector which creates per-installation API clients using ghinstallation.NewFromAppsTransport
  • Extracts addMembersByOrgWithClient from addMembersByOrg so the app auth path can provide an explicit per-installation client

Test plan

  • New unit tests for APIClientForInstallation (3 subtests verifying distinct clients, correct installation ID, different tokens)
  • New unit test for addMembersByOrgWithClient
  • Existing TestAddMembersByApp and TestEnumerateWithApp updated and passing
  • go build passes
  • Manual verification with a multi-org GitHub App installation

Note

Cursor Bugbot is generating a summary for commit 977a541. Configure here.

…kens

When a GitHub App is installed across multiple orgs, addMembersByApp
discovers all installations via JWT but uses a single installation's
token to call ListMembers for every org. GitHub App installation tokens
only bypass IP allowlists for their own org, causing 403 errors for
cross-org calls.

Changes:
- Add appsTransport and apiEndpoint fields to appConnector
- Add APIClientForInstallation method that creates a per-installation
  API client using ghinstallation.NewFromAppsTransport
- Refactor addMembersByApp to create per-installation clients for each
  discovered org instead of reusing s.connector.APIClient()
- Extract addMembersByOrgWithClient so callers can provide an explicit
  client (addMembersByOrg delegates to it for backward compat)
- Update existing tests to pass appConnector and include installation
  IDs in mock responses
- Add new tests for APIClientForInstallation and addMembersByOrgWithClient
@dustin-decker dustin-decker force-pushed the fix/github-app-cross-org-member-enum branch from 07427a9 to 977a541 Compare February 28, 2026 00:31
@dustin-decker dustin-decker marked this pull request as ready for review February 28, 2026 00:46
@dustin-decker dustin-decker requested a review from a team February 28, 2026 00:46
@dustin-decker dustin-decker requested a review from a team as a code owner February 28, 2026 00:46
Copy link
Contributor

@rosecodym rosecodym left a comment

Choose a reason for hiding this comment

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

I think this review will be a bit enmeshed with my (upcoming) review of #4775.

// installation. This is needed when a GitHub App is installed across multiple
// orgs — each org's API calls must use that org's installation token to get
// proper IP allowlist bypass and permission scoping.
func (c *appConnector) APIClientForInstallation(installationID int64) (*github.Client, error) {
Copy link
Contributor

@rosecodym rosecodym Mar 2, 2026

Choose a reason for hiding this comment

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

Can this function be used by NewAppConnector? That function creates a new API client for a particular installation ID (the ID specified in the connection information).

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.

2 participants