Context
PR #3217 adds session-token team narrowing at Layer 1 (visibility / token scoping). When a session token carries a JWT teams claim, resolve_session_teams() narrows the visible scope to the intersection of DB teams and JWT teams.
However, Layer 2 (RBAC / permission checks) is not narrowed to match. When team_id cannot be derived from the request context, PermissionService.check_permission() is called with check_any_team=True, which aggregates permissions from all of the user's team-scoped roles — not just the ones matching the narrowed token_teams.
Problem
A session token narrowed to team A for visibility can still satisfy RBAC checks using roles the user holds in team B.
Example:
- User has
developer role in team A (tools.read, tools.execute)
- User has
team_admin role in team B (teams.*, tools.create, etc.)
- User creates a session narrowed to team A via JWT
teams: ["A"]
- Layer 1: correctly scoped to team A resources only
- Layer 2:
check_any_team=True aggregates roles from both A and B
- Result: user gains
teams.* permissions from team B despite narrowing to team A
Affected Code
mcpgateway/middleware/rbac.py:563 — sets check_any_team=True for session tokens without team_id
mcpgateway/services/permission_service.py:134 — get_user_permissions(..., include_all_teams=check_any_team) does not filter by token_teams
mcpgateway/services/permission_service.py:483 — includes ALL team-scoped roles when include_all_teams=True
Proposed Fix
When check_any_team=True and token_teams is a non-empty list (narrowed session), filter the team-scoped roles in get_user_permissions() to only include roles from teams in token_teams. This preserves existing behavior for un-narrowed sessions (where token_teams covers full DB membership) while correctly restricting narrowed sessions.
Note
This is a pre-existing behavior in the two-layer model, not introduced by #3217. The PR makes it more visible by advertising session-token narrowing that only applies to Layer 1. This issue tracks closing the gap in Layer 2.
Context
PR #3217 adds session-token team narrowing at Layer 1 (visibility / token scoping). When a session token carries a JWT
teamsclaim,resolve_session_teams()narrows the visible scope to the intersection of DB teams and JWT teams.However, Layer 2 (RBAC / permission checks) is not narrowed to match. When
team_idcannot be derived from the request context,PermissionService.check_permission()is called withcheck_any_team=True, which aggregates permissions from all of the user's team-scoped roles — not just the ones matching the narrowedtoken_teams.Problem
A session token narrowed to team A for visibility can still satisfy RBAC checks using roles the user holds in team B.
Example:
developerrole in team A (tools.read, tools.execute)team_adminrole in team B (teams.*, tools.create, etc.)teams: ["A"]check_any_team=Trueaggregates roles from both A and Bteams.*permissions from team B despite narrowing to team AAffected Code
mcpgateway/middleware/rbac.py:563— setscheck_any_team=Truefor session tokens withoutteam_idmcpgateway/services/permission_service.py:134—get_user_permissions(..., include_all_teams=check_any_team)does not filter bytoken_teamsmcpgateway/services/permission_service.py:483— includes ALL team-scoped roles wheninclude_all_teams=TrueProposed Fix
When
check_any_team=Trueandtoken_teamsis a non-empty list (narrowed session), filter the team-scoped roles inget_user_permissions()to only include roles from teams intoken_teams. This preserves existing behavior for un-narrowed sessions (wheretoken_teamscovers full DB membership) while correctly restricting narrowed sessions.Note
This is a pre-existing behavior in the two-layer model, not introduced by #3217. The PR makes it more visible by advertising session-token narrowing that only applies to Layer 1. This issue tracks closing the gap in Layer 2.