Skip to content

Scope API keys to the workspace they were created in - #1622

Merged
helpcodeai merged 2 commits into
mainfrom
fix/mcp-key-workspace-scope
Nov 10, 2026
Merged

helpcodeai merged 2 commits into
mainfrom
fix/mcp-key-workspace-scope

Conversation

@helpcodeai

Copy link
Copy Markdown
Contributor

Summary

An API key belongs to the workspace it was created in, but two paths took the workspace or the role from the owner's active workspace instead (users.organization_id / users.role, which follow the workspace last switched to in the dashboard):

  • MCP keys (resolveUserByKey): the shared /mcp endpoint scoped an unpinned key to the owner's active workspace, so a key created in workspace A listed and ran the tools of workspace B. On /mcp/:serverId, the membership fallback let a key of A reach B's servers.
  • Management keys (JwtOrManagementKeyGuard): the workspace was already the key's, but the role came from users.role, i.e. the role in the active workspace rather than in the key's workspace.

Only owners who belong to several workspaces are affected. Single-workspace users (almost everyone) get exactly the same answer as before, because both values are equal for them.

Changes

  • McpApiKeysService.resolveUserByKey: organizationId is the key's, and role is the owner's membership role in that workspace. The membership is already loaded for the deactivation check, so there is no extra query.
  • McpApiKeysService.generate: a key bound to a server is filed under the server's workspace.
  • JwtOrManagementKeyGuard: the role comes from the membership in the key's workspace.
  • /mcp/:serverId: an API key (authMethod: 'mcp_api_key') passes only when the server is in the key's workspace. Sign-ins (JWT/OAuth) keep the membership fallback for multi-workspace users.
  • McpAuthMiddleware / McpAuthGuard: carry organizationId on req.user like the combined guard does.

Behaviour in both modes: identical for Cloud and self-hosted. Self-hosted instances are usually single-workspace and see no change.

Type

  • Bug fix

Testing

  • Added/updated unit tests:
    • the key's workspace and role win over the active ones (MCP key and support key);
    • a server key is filed under the server's workspace;
    • a management key carries the role in its own workspace (DB integration test);
    • /mcp/:serverId refuses a key on a server of the owner's other workspace and serves it in its own.
  • The new tests fail without the fix (6 failures) and pass with it.
  • packages/backend: npx tsc --noEmit -p tsconfig.json passes, and npm run lint reports 0 errors (11 warnings, all in files this PR does not touch).
  • npx jest --maxWorkers=4, against a scratch database migrated with prisma migrate deploy: 1068 suites passed, 10 skipped (live specs), 32,657 tests passed, 0 failed.
  • Impact checked on Cloud (read-only counts):
    • 1 of 345 active keys belongs to a workspace other than its owner's active one. It is server-bound and unused for 30 days.
    • No management key has a role mismatch.
    • In the last 30 days, every tool call by multi-workspace key owners went to a workspace where they hold a key.

Checklist

  • My code follows the existing code style
  • I have tested my changes locally
  • All existing tests pass
  • This PR has a descriptive title

An MCP key resolved the owner's active workspace (users.organization_id) and role (users.role) instead of the key's own workspace, so a key created in one workspace reached another one its owner had switched to. Take the workspace from the key and the role from the owner's membership there, for MCP keys and management keys. On /mcp/:serverId an API key only reaches servers of its own workspace, and a server-bound key is filed under the server's workspace.
@helpcodeai
helpcodeai merged commit 20b55f9 into main Nov 10, 2026
16 checks passed
@helpcodeai
helpcodeai deleted the fix/mcp-key-workspace-scope branch November 10, 2026 16:09
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant