fix: gate MCP server actions and mutations on org membership and role (#2040)#2249
Open
larryro wants to merge 1 commit into
Open
fix: gate MCP server actions and mutations on org membership and role (#2040)#2249larryro wants to merge 1 commit into
larryro wants to merge 1 commit into
Conversation
Collaborator
Author
Desk Review — #2249 (closes #2040)Verdict: READY TO MERGE. The fix correctly and completely closes the cross-tenant authorization gap on the MCP-server public surface. Reviewed via a two-round fan-out (6 breadth reviewers across correctness/robustness/tests/elegance/security, then independent adversarial confirmation of the two highest-impact claims). No blocking findings. Evidence
What's correct
Non-blocking observations (optional follow-ups, do not gate merge)
None of the above blocks the merge. READY TO MERGE. |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Closes #2040
Problem
The MCP-server backend exposed several public Convex actions/mutations that checked only authentication (or nothing at all), giving any authenticated user in any org a cross-tenant primitive:
executeMcpTool(actions.ts) had no auth check and was a publicapi.*action. It fetched another org's full server doc (including encrypted credentials) and executed a named tool againstserver.urlwith the decrypted bearer/OAuth secret — a cross-tenant credential-use + SSRF primitive.testConnection(actions.ts) checked onlygetAuthUserIdentity, then ran discovery against another org's server with its stored credentials and persisted status/token updates.create/update/remove/updateStatus(public_mutations.ts) checked onlygetAuthUserIdentity;createtrusted a client-suppliedorganizationId, the id-based ones never confirmed the server belonged to the caller's org.Fix
Mirrors the proven pattern from the sibling sandbox admin surface (
node_only/sandbox/session_admin_actions.ts) and the MCP read queries (queries.ts):executeMcpTool→internalAction. Its only callers are trusted backend paths (agent_tools/mcp/create_bound_mcp_tool.tsandmcp_servers/execute_approved.ts), now invoked viainternal.mcp_servers.actions.executeMcpTool. This removes the public exposure entirely — closing the SSRF/credential-use hole.testConnectionandpublic_mutations.*stay public (they are wired to the frontend viauseAction) but are now gated server-side withrequireOrgAdminOrDeveloper:createenforces the capability on the suppliedorganizationId.update/remove/updateStatus/testConnectionresolve the owning org from the stored server document and gate on that, so the ownership match is implicit — a member of another org cannot touch the server (therequireSessionInOrgdefense-in-depth pattern).Verification
convex/mcp_servers/authorization.test.ts(13 cases): each public mutation/action rejects an unauthorized caller and never mutates;creategates on the supplied org; id-based ops gate on the owning org and throw on a missing server; assertsexecuteMcpToolis aninternalAction.bunx vitest --run --project server mcp_servers/— 35 passed.bunx tsc --noEmit— clean (the public/internal type split flows from the function visibility; no codegen change needed).bunx oxlint --type-awareon all changed files — 0 warnings, 0 errors.