Repository navigation
fix(rbac): derive create ownership from the authenticated caller - #7296
msureshkumar88 wants to merge 11 commits into
Conversation
|
mcpgateway/admin.py:15258: admin_import_tools lets a public-only token create team or private tools in the caller's personal team. /tools returns 403 for this. Apply the same deny rule here and add a deny-path test. mcpgateway/main.py:1031: _CREATE_TEAM_DENIED also fires on RBAC denial, so its "API token" wording is wrong for session and global-role users. Use a separate 403 message for that case, for example "Missing in the requested team". |
2e6476d to
9ed5661
Compare
|
Thanks for the review. Both points are fixed in fbc4e60.
|
fbc4e60 to
8c9a5d0
Compare
8c9a5d0 to
df1d5ca
Compare
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
The register methods for servers, resources, prompts and tools read team_id and owner_email from the create payload before the function parameters. Use the parameters only, including the duplicate-name checks and the bulk variants. register_prompt now prefers the visibility parameter over the payload value. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
Add live-gateway tests for the server, resource, prompt and tool create routes. The tests check that a caller creates only in a team its token is scoped to, that the stored owner is the caller, and that a public-only token never assigns a team. Let isolated_gateway accept environment overrides through indirect parametrization. The new module turns off personal teams with it. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
…review Update paths check team access before name or URI conflicts. A caller can no longer learn that a name or URI exists in a team the caller cannot use. - update_server, update_resource and update_prompt check ownership, then the target team, then name or URI conflicts. - The gRPC create route returns 403 when the payload team_id is not a team of the caller. - Discovered resources and prompts inherit team_id and owner_email from the gateway. - Add the nested-override live test, a session-token case, and unit tests for each change. The sibling-route test accepts a 403 refusal. Same-team requests keep their status codes and response bodies. Cross-team updates that returned 409 now return the team validation error. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
- Bulk tool import denies team and private tools for public-only tokens and stores public tools without a team, the same as POST /tools. - _resolve_create_scope() returns "Missing <permission> in the requested team." when RBAC denies the create. The API-token wording stays for token-scope mismatches. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
df1d5ca to
a6b3c7d
Compare
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
_is_jwt_token() rejects tokens without three base64url parts, so the placeholder token made these tests fail on main as well. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
🐛 Bug-fix PR
📌 Summary
Create routes and admin tool paths now derive record ownership from the authenticated caller. They no longer accept ownership fields from the request payload without authorization.
The update routes, the gRPC create route and gateway discovery follow the same rule. A caller cannot learn about records in a team the caller cannot use. Records that gateway discovery creates keep the gateway's team and owner.
🔁 Reproduction Steps
Found in an internal review. No public issue is linked.
🐞 Root Cause
team_idonly.register_*services preferred the payloadteam_idandowner_emailover the values the route resolved.team_idwithout a membership check.team_idandowner_email.💡 Fix Description
_resolve_create_scope()inmcpgateway/main.py.POST /servers,/resources,/promptsand/toolsuse it.team_id/visibilitywith the top-level fields.token_teamsmembership check.check_permission_inline().register_server,register_resource,register_prompt,register_tooland the bulk variants ignore the payloadteam_id/owner_email. The owner is theowner_emailargument, or elsecreated_by./import. Each item runs the public-visibility check.update_server,update_resourceandupdate_promptcheck ownership, then the target team, then the name or URI conflict.POST /admin/grpcreturns 403 when the payloadteam_idis not a team of the caller.team_idandowner_emailfrom the gateway.Behavior changes
team_idthat differs from the top-levelteam_idreturns 403.team_idoutside the token's teams returns 403. A nestedteam_idwhere the caller lacks the create permission also returns 403.visibilityofteamorprivatereturns 403.owner_emailis ignored. The owner is the caller.POST /resourceshonors the nestedvisibility. The OpenAPI default of the top-levelvisibilityfor resources and prompts is now unset. The effective default stayspublic.visibilitywins over the nested value, the same as for the other kinds.team_id/owner_email.POST /admin/grpcwith ateam_idoutside the caller's teams returns 403.Requests inside the caller's own team keep their status codes and response bodies.
📏 Reviewability
triage(no public issue)🧪 Verification
make ruff interrogate pylintrate_limit_middleware.py, not changed here.make testtest_config.pyCSRF tests fail under xdist and pass in isolation. This PR does not change config.make coverage+diff-cover --compare-branch=origin/maintests/live_gateway/e2e/test_create_ownership_scope.pytests/live_gateway/e2e/test_server_create_nested_team_override.pytest_admin.py,test_main.py) andmake ruffpytest -n 4on those filesmake test,make coverageand the lint run above predate it.make docker-prod-rust testing-up+make test-e2e📐 MCP Compliance (if relevant)
✅ Checklist