You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR adds an optional GITLAB_ALLOWED_GROUPS environment variable that restricts
MCP OAuth server access to users belonging to specific GitLab groups or their
subgroups. When configured, any authenticated user who is not a member of a
matching group receives a 401 Access Denied response.
Changes
Modified Files
config.ts (7 lines added)
Added GITLAB_ALLOWED_GROUPS export: parses a comma-separated list of group
full paths from the env var or --allowed-groups CLI flag
oauth-proxy.ts (38 lines added, 8 lines changed)
Added _allowedGroups field and allowedGroups constructor parameter to GitLabOAuthServerProvider
Added group check in exchangeAuthorizationCode() (both passthrough and
callback proxy paths) — runs once at token issuance, not on every request
Added _checkGroupMembership(): paginates GET /api/v4/groups with the
user's own bearer token and checks if any returned group's full_path
equals or is a sub-path of an allowed group
Updated createGitLabOAuthProvider() factory signature to accept allowedGroups
index.ts (3 lines changed)
Added GITLAB_ALLOWED_GROUPS to the config import list
Passed GITLAB_ALLOWED_GROUPS through to createGitLabOAuthProvider()
Modified Docs
docs/environment-variables.md (28 lines added)
Documented GITLAB_ALLOWED_GROUPS with examples, notes on inheritance
behaviour, case sensitivity, and security properties
test/mcp-oauth-tests.ts (120 lines added)
New describe block: "MCP OAuth — Group Allowlist"
9 unit tests covering: no restriction (default), direct member, first-level
subgroup, nested subgroup, non-member rejection, prefix-spoofing rejection,
multiple allowed groups, pagination (match on page 2), case-insensitive
matching
Implementation Details
Access Check Logic
During token issuance (exchangeAuthorizationCode), when GITLAB_ALLOWED_GROUPS
is set:
Exchange the authorization code with GitLab for an access token (existing step)
Call GET /api/v4/groups?min_access_level=10&per_page=100&page=N using the
newly issued access token (no extra service account needed)
For each returned group check if full_path equals any allowed group path or
starts with <allowed>/
Paginate until a match is found or all pages are exhausted
Reject with an error if no match — the client never receives a token
No overhead on subsequent requests: the check does not run on verifyAccessToken.
Inheritance Behaviour
Configured
User's group
Allowed?
my-org
my-org
✅ direct member
my-org
my-org/team-a
✅ subgroup member
my-org
my-org/team-a/squad-1
✅ nested subgroup
my-org
my-org2
❌ prefix-spoofing rejected
my-org/team-a
my-org
❌ ancestor not granted
Security Properties
No squatting: GitLab group full_path values are globally unique on an
instance — a duplicate path cannot be registered
No prefix spoofing: my-org2 does not match my-org (separator / is
required after the prefix)
No server-side secret: the user's own OAuth token is used for the group
lookup; no additional credentials are stored
Immediate enforcement: because verifyAccessToken runs on every request
in stateful mode, the restriction takes effect the moment the new binary is
deployed — there is no grace period for existing sessions
Testing & Quality Assurance
✅ TypeScript Compilation: npx tsc --noEmit — no errors
✅ Unit Tests: 9 new test cases on exchangeAuthorizationCode covering all
acceptance, rejection, and edge cases (pagination, case-insensitivity, prefix spoofing)
✅ Backward Compatible: GITLAB_ALLOWED_GROUPS is unset by default; existing
deployments are unaffected
Usage
# Restrict to a single top-level group (and all its subgroups)
GITLAB_ALLOWED_GROUPS=my-company
# Restrict to specific subgroups only
GITLAB_ALLOWED_GROUPS=my-company/engineering,my-company/security
# CLI flag equivalent
--allowed-groups my-company/engineering
Impact Assessment
Breaking Changes
None. The feature is opt-in via GITLAB_ALLOWED_GROUPS. Deployments without
this variable behave exactly as before.
Performance
The group check runs once per OAuth flow (token issuance), not on every request.
In the common case (user in the allowed group, result on page 1) this is one
additional round-trip during login only — zero overhead per tool call.
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. Credits must be used to enable repository wide code reviews.
Hi, @zereight. Have you considered setting up code-rabbit for PR reviews? It's free for OSS projects. Although, I'm not sure about the review quality/accuracy.
@vrajpal-jhala thanks for comment, i moved to cursor from codex, so codex limit occured.
i knew some days ago, cursor bugbot can not be triggered automatically.
i think code rabbit is good option! i will consider it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This PR adds an optional
GITLAB_ALLOWED_GROUPSenvironment variable that restrictsMCP OAuth server access to users belonging to specific GitLab groups or their
subgroups. When configured, any authenticated user who is not a member of a
matching group receives a
401 Access Deniedresponse.Changes
Modified Files
config.ts (7 lines added)
GITLAB_ALLOWED_GROUPSexport: parses a comma-separated list of groupfull paths from the env var or
--allowed-groupsCLI flagoauth-proxy.ts (38 lines added, 8 lines changed)
_allowedGroupsfield andallowedGroupsconstructor parameter toGitLabOAuthServerProviderexchangeAuthorizationCode()(both passthrough andcallback proxy paths) — runs once at token issuance, not on every request
_checkGroupMembership(): paginatesGET /api/v4/groupswith theuser's own bearer token and checks if any returned group's
full_pathequals or is a sub-path of an allowed group
createGitLabOAuthProvider()factory signature to acceptallowedGroupsindex.ts (3 lines changed)
GITLAB_ALLOWED_GROUPSto the config import listGITLAB_ALLOWED_GROUPSthrough tocreateGitLabOAuthProvider()Modified Docs
docs/environment-variables.md (28 lines added)
GITLAB_ALLOWED_GROUPSwith examples, notes on inheritancebehaviour, case sensitivity, and security properties
test/mcp-oauth-tests.ts (120 lines added)
describeblock: "MCP OAuth — Group Allowlist"subgroup, nested subgroup, non-member rejection, prefix-spoofing rejection,
multiple allowed groups, pagination (match on page 2), case-insensitive
matching
Implementation Details
Access Check Logic
During token issuance (
exchangeAuthorizationCode), whenGITLAB_ALLOWED_GROUPSis set:
GET /api/v4/groups?min_access_level=10&per_page=100&page=Nusing thenewly issued access token (no extra service account needed)
full_pathequals any allowed group path orstarts with
<allowed>/No overhead on subsequent requests: the check does not run on
verifyAccessToken.Inheritance Behaviour
my-orgmy-orgmy-orgmy-org/team-amy-orgmy-org/team-a/squad-1my-orgmy-org2my-org/team-amy-orgSecurity Properties
full_pathvalues are globally unique on aninstance — a duplicate path cannot be registered
my-org2does not matchmy-org(separator/isrequired after the prefix)
lookup; no additional credentials are stored
verifyAccessTokenruns on every requestin stateful mode, the restriction takes effect the moment the new binary is
deployed — there is no grace period for existing sessions
Testing & Quality Assurance
✅ TypeScript Compilation:
npx tsc --noEmit— no errors✅ Unit Tests: 9 new test cases on
exchangeAuthorizationCodecovering allacceptance, rejection, and edge cases (pagination, case-insensitivity, prefix spoofing)
✅ Backward Compatible:
GITLAB_ALLOWED_GROUPSis unset by default; existingdeployments are unaffected
Usage
Impact Assessment
Breaking Changes
None. The feature is opt-in via
GITLAB_ALLOWED_GROUPS. Deployments withoutthis variable behave exactly as before.
Performance
The group check runs once per OAuth flow (token issuance), not on every request.
In the common case (user in the allowed group, result on page 1) this is one
additional round-trip during login only — zero overhead per tool call.
Checklist
docs/environment-variables.md)