GitHub Provider Session Enrichment#
During authentication, the GitHub provider's EnrichSession method makes a sequence of GitHub API calls to populate the session with user data: org/team memberships, email address, and username. The enrichment sequence is:
getOrgAndTeamβ fetches all orgs and teams, populatessession.GroupscheckRestrictionsβ enforces any configured org/team/repo access rulesgetEmailβ fetches the user's primary verified email via/user/emailsgetUserβ fetches the username via/user, and optionally checks collaborator status
The central concern for operators is step 1: getOrgAndTeam runs unconditionally on every enrichment, regardless of whether org/team restrictions are configured.
Org/Team API Calls: What They Do and What They Cost#
getOrgs paginates GET /user/orgs (100 per page) and appends each org login (e.g., "my-org") to session.Groups. getTeams paginates GET /user/teams (100 per page) and appends org:team-slug entries (e.g., "my-org:backend"). Both loops run until GitHub returns an empty page.
Cost implications:
- Every login and session refresh triggers at minimum two outbound API calls to
api.github.com(or the GitHub Enterprise Server equivalent). - Users with many orgs or teams generate additional paginated requests.
- There is no caching of membership data between requests.
- The default OAuth scope
user:email read:orgcovers these calls, but if a deployment overrides the scope to onlyuser:email, bothgetOrgsandgetTeamswill fail β andEnrichSessiontreats these failures as hard errors, not warnings.
Why Calls Are Unconditional: X-Forwarded-Groups Support#
Before PR #2196 (merged December 2023), org/team data was only fetched when --github-org or --github-team access restrictions were configured. PR #2196 switched fetching to always-on so that all of a user's GitHub org/team memberships are unconditionally populated in session.Groups and forwarded downstream as the X-Forwarded-Groups header β regardless of whether restrictions are in use.
Groups are formatted as comma-separated org:team pairs (e.g., org1:team1,org1:team2,org2:team1), with bare org names included for org-only memberships.
The access-control logic in checkRestrictions continues to work as before β it reads from the already-populated session.Groups rather than issuing new API calls.
Known Issue and Proposed Mitigations#
Issue #3482 (opened 2026-07-26) tracks this behavior as a bug/limitation. The issue is open with no fix merged as of this writing.
Three mitigation approaches have been identified by maintainers :
| Approach | Description |
|---|---|
| Scope-gated enrichment | Skip getOrgAndTeam if read:org is not in the configured scope |
| Restriction-gated enrichment | Only fetch when p.Org or p.Team is set (reverts to pre-#2196 behavior, possibly flag-guarded) |
| Graceful failure | Treat org/team API errors as non-fatal when no restrictions are configured |
Workaround today: If you do not need org/team data, avoid overriding the scope to omit read:org β the calls will still happen, but they won't hard-fail. There is no configuration flag to suppress the fetching entirely.
Key Source References#
| Reference | Purpose |
|---|---|
providers/github.go | Full GitHub provider implementation |
EnrichSession | Entry point for session enrichment |
getOrgAndTeam | Dispatcher that calls getOrgs + getTeams |
getOrgs | Paginated /user/orgs fetch |
getTeams | Paginated /user/teams fetch |
checkRestrictions | Access control using already-populated groups |
| PR #2196 | Introduced unconditional group fetching for X-Forwarded-Groups |
| Issue #3482 | Open issue tracking performance/scope problem |