mastra-ai/mastra · warning · HTTPException

You do not have permission to disconnect this connection

Error message

You do not have permission to disconnect this connection

What it means

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.

Source

Thrown at packages/server/src/server/handlers/tool-providers.ts:612

      let ownerAuthorId: string | undefined;
      let ownerScope: 'shared' | 'per-author' | 'caller-supplied' | undefined;
      let matched = false;
      if (store) {
        const rows = await store.listConnectionsByAuthor({ providerId: provider.info.id });
        const match = rows.find(r => r.connectionId === connectionId);
        if (match) {
          matched = true;
          ownerAuthorId = match.authorId;
          ownerScope = match.scope;
        }
      }

      // Fail closed: if storage is configured and no row matches the
      // requested connectionId, refuse the call for non-admins. Without
      // this guard, a caller could trigger provider-side `revokeConnection`
      // against another tenant's connectionId by guessing it.
      if (store && !matched && !isAdmin) {
        throw new HTTPException(403, {
          message: 'You do not have permission to disconnect this connection',
        });
      }

      const effectiveOwner = ownerAuthorId ?? callerAuthorId;
      const isShared = ownerScope === 'shared';
      if (!isShared && effectiveOwner !== callerAuthorId && !isAdmin) {
        throw new HTTPException(403, {
          message: 'You do not have permission to disconnect this connection',
        });
      }

      if (!isForce) {
        const usage = await countConnectionUsage(mastra, connectionId);
        if (usage > 0) {
          throw new HTTPException(409, {
            message: `Connection ${connectionId} is still pinned by ${usage} agent(s). Pass ?force=true to disconnect anyway.`,
          });

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Verify the connectionId matches a row visible in storage for this server (list connections via listConnections first).
  2. Ensure the same storage/database is configured on the server instance handling the disconnect.
  3. Re-sync or remove stale client-side connection references after rows were deleted.
  4. If the disconnect is legitimately cross-tenant (operations), perform it with admin credentials that pass hasAdminBypass.

Example fix

// before
await api.disconnect({ providerId: 'acme', connectionId: params.id }); // id from old cache
// after
const connections = await api.listConnections('acme');
if (connections.some(c => c.connectionId === params.id)) {
  await api.disconnect({ providerId: 'acme', connectionId: params.id });
}
Defensive patterns

Strategy: validation

Validate before calling

const connections = await api.listConnections(providerId);
if (!connections.some(c => c.connectionId === connectionId)) {
  console.warn(`Connection ${connectionId} not in storage; skipping disconnect`);
  return;
}

Try / catch

try {
  await api.disconnect({ providerId, connectionId });
} catch (e) {
  if (e.status === 403 && /permission to disconnect/.test(e.message)) {
    // refresh stored connections / notify user the connection is unknown
  } else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/67fa376c2f548ea7. Report an issue: GitHub.