fix(security): enforce team membership validation in admin UI#3399
fix(security): enforce team membership validation in admin UI#3399crivetimihai merged 6 commits intomainfrom
Conversation
a476370 to
2af20d3
Compare
|
Thanks @madhav165 — important security fix. Replacing the permissive fallback with strict 403 rejection is the correct approach. Three unit tests covering the edge cases is good. LGTM. |
There was a problem hiding this comment.
PR Review: Team Scoping Validation Fix (PR #3399)
Executive Summary
Verdict: ✅ APPROVED
Architecture Compliance
Two-Layer Security Model:
Layer 1 (Token Scoping) → Layer 2 (RBAC) → Operation
↓ ↓
normalize_token_teams() require_permission()
↓ ↓
[PASS] [PASS] → admin_ui() validates team_id
This fix operates at the application layer (after both security layers):
- Token scoping already filtered what user CAN SEE
- RBAC already checked if user CAN ACCESS admin dashboard
- This fix ensures user can't REQUEST a team context they don't belong to
Correct placement in security stack:
- ✅ After authentication (
get_current_user_with_permissions) - ✅ After RBAC check (
@require_permission("admin.dashboard")) - ✅ Before data fetching (lines 3227+)
🚨 Potential Issues & Mitigations
Issue 1: Breaking Change for Existing Clients
Risk: Low
Impact: Clients relying on fallback behavior will now get 403
Mitigation: This is the intended behavior - the fallback was a bug. Clients should not be requesting team contexts they don't belong to.
Issue 2: User Experience
Risk: Low
Impact: Users might see 403 instead of empty results
Mitigation: Error messages are clear ("Not a member of the requested team"). This is better UX than silently showing different data.
Issue 3: Service Availability
Risk: Low
Impact: If team service fails, users can't access admin UI with team filter
Mitigation: Users can still access without team_id parameter. This is correct fail-closed behavior.
🎓 Recommendations
For Merge
✅ APPROVE - This PR should be merged as-is.
Rationale:
- Fixes the issue
- Implementation is correct and minimal
- Test coverage is comprehensive
- Aligns with project security principles
- No breaking changes to legitimate use cases
Test Running Results:
tests/unit/mcpgateway/test_admin_module.py::test_admin_ui_rejects_invalid_team_id PASSED
tests/unit/mcpgateway/test_admin_module.py::test_admin_ui_rejects_team_id_when_teams_unavailable PASSED
tests/unit/mcpgateway/test_admin_module.py::test_admin_ui_no_team_id_returns_public_items PASSED
🏆 Final Verdict
APPROVED ✅
Recommendation: Merge immediately
2af20d3 to
a015922
Compare
…k to public visibility When a team_id query param does not match the user's teams, the admin UI silently dropped the team filter and returned public entities. Now returns 403 Forbidden for both invalid team_id and failed team lookup cases. Adds regression tests for invalid, unavailable, and null team_id paths. Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
a015922 to
dde19cb
Compare
Platform admins should be able to view any team's resources in the admin UI, consistent with the codebase-wide admin bypass pattern (token scoping, RBAC middleware, permission service all skip team checks for admins). - Add is_admin check before team membership validation so platform admins can select any team_id without being a member - Rename test_admin_ui_team_loading_failure_ignores_team_id to test_admin_ui_team_loading_failure_rejects_team_id (matches new 403 behavior) - Add test_admin_ui_admin_bypasses_team_membership_check for coverage of the admin bypass path Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
The admin bypass must check both is_admin AND token_teams is None, consistent with the codebase convention (admin.py:13253, base_service.py, token scoping middleware). A team-scoped admin token (is_admin=True, token_teams=["team-1"]) must still pass membership validation. - Check _token_teams is None alongside _is_admin for the bypass - Update comment to document the token_teams requirement - Add test for team-scoped admin rejection (token_teams=["team-1"]) - Update admin bypass test to set token_teams=None explicitly Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
dde19cb to
1e5a2f2
Compare
Review SummaryRebased onto main (clean), reviewed, and applied fixes. Original PR (correct)
Review fixes applied1. Admin bypass with token_teams check 2. Test name correction 3. Additional tests
4. Docstring Raises section Known pre-existing limitation (not in scope)
Verification
|
crivetimihai
left a comment
There was a problem hiding this comment.
Approved. Security fix is correct — 403 rejection for unauthorized team_id with proper admin bypass (is_admin + token_teams=None). All branches tested.
* fix(security): reject invalid team_id with 403 instead of falling back to public visibility When a team_id query param does not match the user's teams, the admin UI silently dropped the team filter and returned public entities. Now returns 403 Forbidden for both invalid team_id and failed team lookup cases. Adds regression tests for invalid, unavailable, and null team_id paths. Signed-off-by: Madhav Kandukuri <madhav165@gmail.com> * pytest and pylint fixes Signed-off-by: Madhav Kandukuri <madhav165@gmail.com> * fix(security): add admin bypass to team_id validation and fix test name Platform admins should be able to view any team's resources in the admin UI, consistent with the codebase-wide admin bypass pattern (token scoping, RBAC middleware, permission service all skip team checks for admins). - Add is_admin check before team membership validation so platform admins can select any team_id without being a member - Rename test_admin_ui_team_loading_failure_ignores_team_id to test_admin_ui_team_loading_failure_rejects_team_id (matches new 403 behavior) - Add test_admin_ui_admin_bypasses_team_membership_check for coverage of the admin bypass path Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(security): update comment to reflect admin bypass behavior Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(security): add HTTPException to admin_ui docstring Raises section Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(security): tighten admin bypass to require unrestricted token_teams The admin bypass must check both is_admin AND token_teams is None, consistent with the codebase convention (admin.py:13253, base_service.py, token scoping middleware). A team-scoped admin token (is_admin=True, token_teams=["team-1"]) must still pass membership validation. - Check _token_teams is None alongside _is_admin for the bypass - Update comment to document the token_teams requirement - Add test for team-scoped admin rejection (token_teams=["team-1"]) - Update admin bypass test to set token_teams=None explicitly Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Madhav Kandukuri <madhav165@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
* fix(security): reject invalid team_id with 403 instead of falling back to public visibility When a team_id query param does not match the user's teams, the admin UI silently dropped the team filter and returned public entities. Now returns 403 Forbidden for both invalid team_id and failed team lookup cases. Adds regression tests for invalid, unavailable, and null team_id paths. Signed-off-by: Madhav Kandukuri <madhav165@gmail.com> * pytest and pylint fixes Signed-off-by: Madhav Kandukuri <madhav165@gmail.com> * fix(security): add admin bypass to team_id validation and fix test name Platform admins should be able to view any team's resources in the admin UI, consistent with the codebase-wide admin bypass pattern (token scoping, RBAC middleware, permission service all skip team checks for admins). - Add is_admin check before team membership validation so platform admins can select any team_id without being a member - Rename test_admin_ui_team_loading_failure_ignores_team_id to test_admin_ui_team_loading_failure_rejects_team_id (matches new 403 behavior) - Add test_admin_ui_admin_bypasses_team_membership_check for coverage of the admin bypass path Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(security): update comment to reflect admin bypass behavior Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(security): add HTTPException to admin_ui docstring Raises section Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(security): tighten admin bypass to require unrestricted token_teams The admin bypass must check both is_admin AND token_teams is None, consistent with the codebase convention (admin.py:13253, base_service.py, token scoping middleware). A team-scoped admin token (is_admin=True, token_teams=["team-1"]) must still pass membership validation. - Check _token_teams is None alongside _is_admin for the bypass - Update comment to document the token_teams requirement - Add test for team-scoped admin rejection (token_teams=["team-1"]) - Update admin bypass test to set token_teams=None explicitly Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Madhav Kandukuri <madhav165@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com> Signed-off-by: Yosief Eyob <yosiefogbazion@gmail.com>
🐛 Bug-fix PR
Fixes https://github.ibm.com/contextforge-org/mcp-context-forge/issues/44
📌 Summary
Fixes a team scoping validation issue in the admin UI where an invalid
team_idquery parameter could result in broader visibility than intended. The endpoint now correctly enforces team membership checks and rejects unauthorized team contexts.🐞 Root Cause
The
admin_uiendpoint inmcpgateway/admin.pyhad a permissive fallback path during team validation that silently relaxed scoping constraints instead of enforcing them.💡 Fix Description
admin_uifunction; the default no-team-id path remains unchanged.🧪 Verification
make lintmake testmake coverage📐 MCP Compliance (if relevant)
✅ Checklist
make black isort pre-commit)