{"record":{"id":"67fa376c2f548ea7","repo":"mastra-ai/mastra","slug":"you-do-not-have-permission-to-disconnect-this-conn","errorCode":null,"errorMessage":"You do not have permission to disconnect this connection","messagePattern":"You do not have permission to disconnect this connection","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"warning","filePath":"packages/server/src/server/handlers/tool-providers.ts","lineNumber":612,"sourceCode":"      let ownerAuthorId: string | undefined;\n      let ownerScope: 'shared' | 'per-author' | 'caller-supplied' | undefined;\n      let matched = false;\n      if (store) {\n        const rows = await store.listConnectionsByAuthor({ providerId: provider.info.id });\n        const match = rows.find(r => r.connectionId === connectionId);\n        if (match) {\n          matched = true;\n          ownerAuthorId = match.authorId;\n          ownerScope = match.scope;\n        }\n      }\n\n      // Fail closed: if storage is configured and no row matches the\n      // requested connectionId, refuse the call for non-admins. Without\n      // this guard, a caller could trigger provider-side `revokeConnection`\n      // against another tenant's connectionId by guessing it.\n      if (store && !matched && !isAdmin) {\n        throw new HTTPException(403, {\n          message: 'You do not have permission to disconnect this connection',\n        });\n      }\n\n      const effectiveOwner = ownerAuthorId ?? callerAuthorId;\n      const isShared = ownerScope === 'shared';\n      if (!isShared && effectiveOwner !== callerAuthorId && !isAdmin) {\n        throw new HTTPException(403, {\n          message: 'You do not have permission to disconnect this connection',\n        });\n      }\n\n      if (!isForce) {\n        const usage = await countConnectionUsage(mastra, connectionId);\n        if (usage > 0) {\n          throw new HTTPException(409, {\n            message: `Connection ${connectionId} is still pinned by ${usage} agent(s). Pass ?force=true to disconnect anyway.`,\n          });","sourceCodeStart":594,"sourceCodeEnd":630,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/tool-providers.ts#L594-L630","documentation":"HTTP 403 fail-closed guard: when storage is configured and no stored connection row matches the requested connectionId, non-admin callers are refused before provider-side revokeConnection is invoked. This prevents an attacker from triggering revocation against another tenant's connectionId by guessing ids.","triggerScenarios":"POST/DELETE disconnect route (tool-providers.ts:612) where the storage lookup finds no row for connectionId and the caller lacks admin bypass (hasAdminBypass for TOOL_PROVIDERS_RESOURCE).","commonSituations":"Connection row deleted from storage while still existing provider-side (stale client cache); storage not shared between the server handling the request and where the connection was created; wrong connectionId (typo or id from another environment); non-admin user trying to revoke someone else's connection.","solutions":["Verify the connectionId matches a row visible in storage for this server (list connections via listConnections first).","Ensure the same storage/database is configured on the server instance handling the disconnect.","Re-sync or remove stale client-side connection references after rows were deleted.","If the disconnect is legitimately cross-tenant (operations), perform it with admin credentials that pass hasAdminBypass."],"exampleFix":"// before\nawait api.disconnect({ providerId: 'acme', connectionId: params.id }); // id from old cache\n// after\nconst connections = await api.listConnections('acme');\nif (connections.some(c => c.connectionId === params.id)) {\n  await api.disconnect({ providerId: 'acme', connectionId: params.id });\n}","handlingStrategy":"validation","validationCode":"const connections = await api.listConnections(providerId);\nif (!connections.some(c => c.connectionId === connectionId)) {\n  console.warn(`Connection ${connectionId} not in storage; skipping disconnect`);\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await api.disconnect({ providerId, connectionId });\n} catch (e) {\n  if (e.status === 403 && /permission to disconnect/.test(e.message)) {\n    // refresh stored connections / notify user the connection is unknown\n  } else throw e;\n}","preventionTips":["Refresh the connection list before disconnecting instead of using cached ids.","Ensure all server instances share the same storage backend.","Clean up client references when connections are removed from storage."],"tags":["http-403","tool-providers","authorization","multi-tenancy","fail-closed"],"backgroundTag":"insufficient-permissions","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}