v0.6.0 — Connection grants, and a cross-tenant fix
⚠️ Security — upgrade if you run multi-tenant
An authenticated user could execute another tenant's connector, with that tenant's stored credentials and base URL, by owning a tool of the same name. Reproduced on our own cloud instance: two fresh workspaces, each with their own connector, both executed a third unrelated workspace's connector.
DynamicMcpTools.executeTool resolved the tool with an unfiltered lookup first and only fell back to the organization if (!tool). On the shared /mcp there are no connector ids, and an unfiltered lookup returns whichever connector registered that name first, in any organization — so the org-scoped fallback never ran. The intent was right; the order was inverted.
The three scopes are now mutually exclusive rather than a fallback chain, and none widens when its own scope has no match:
| caller | scope |
|---|---|
/mcp/:serverId, or an API key pinned to a server |
that server's connectors |
any JWT on the shared /mcp |
the caller's organization |
| instance-level static credential (single-tenant self-hosted) | unscoped |
Not a data-read leak — the caller gets that connector's API response, not the other tenant's rows. Worse in one respect: with a write tool it acts as them against their own upstream system. It required a tool-name collision, which is trivial to arrange; 610 tool names were shared across more than one organization on our instance, one by 76.
Connection grants — choose what each AI client can reach
The shared /mcp endpoint now serves what you granted, and only that.
- A picker during authorization, shown only when there is genuinely a choice. One server, or a client that already named one, is granted silently — most people will never see it.
- A Connections page in Settings: change or revoke what a connected client reaches, taking effect on its next request. This matters because clients reuse cached tokens (Claude: 1-day access, 30-day refresh), so disconnecting and reconnecting does not reliably re-open the choice.
- Grants only ever narrow. Membership is re-validated against the database on every request, so a grant naming a workspace you have since left concedes nothing.
Tokens issued before this release are unaffected: no grant means the previous behaviour.
Fixes
tools/liston the shared/mcpfailed outright for any workspace whose tools declare adate/date-timeparameter — aZodDatehas no JSON Schema representation, and one throw ended the whole response. 91 tools across 9 workspaces on our instance could not use the endpoint at all.- Boot no longer holds two copies of the tool registry. The single query that loaded every tenant's connectors materialised the whole result set alongside the registry it was filling, roughly doubling peak heap; it crash-looped our cloud instance nine times. Now paged.
- A flaky upstream looks like one. Connection-level failures are restated in plain language instead of a raw OpenSSL dump, the retry budget stretches to 3.7 s, and exhausting it logs a warning instead of a debug line nobody sees in production.
- Connectors that could never work are refused at creation, with a message that says what to do — including naming the Authentication section when the value looks like a pasted API key. New connectors are also attached to an MCP server on creation, instead of being left reachable by nobody.