From e93a5caf5ca4e738de2184b9a31a3819eb449dd3 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 15 Jun 2026 16:14:07 -0700 Subject: [PATCH] fix(credential-sets): stop leaking open-invite tokens to all users GET /api/credential-sets/invitations returned every pending, unexpired link-only (null-email) invitation across all organizations, including the bearer token. Any authenticated user could enumerate and accept another org's invitation, joining its credential set (cross-tenant access). Scope the listing strictly to invitations addressed to the caller's own email. Open-link invites remain redeemable only via the out-of-band /credential-account/[token] URL. --- apps/sim/app/api/credential-sets/invitations/route.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/sim/app/api/credential-sets/invitations/route.ts b/apps/sim/app/api/credential-sets/invitations/route.ts index 2ad4eb23d11..a6da9ca82b2 100644 --- a/apps/sim/app/api/credential-sets/invitations/route.ts +++ b/apps/sim/app/api/credential-sets/invitations/route.ts @@ -1,7 +1,7 @@ import { db } from '@sim/db' import { credentialSet, credentialSetInvitation, organization, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' -import { and, eq, gt, isNull, or } from 'drizzle-orm' +import { and, eq, gt } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -16,6 +16,9 @@ export const GET = withRouteHandler(async () => { } try { + // Scope to invitations addressed to the caller's own email only. Open-link + // (null-email) invites carry a bearer token redeemed via the out-of-band URL + // and must never be listed, or any user could accept another org's invite. const invitations = await db .select({ invitationId: credentialSetInvitation.id, @@ -37,10 +40,7 @@ export const GET = withRouteHandler(async () => { .leftJoin(user, eq(credentialSetInvitation.invitedBy, user.id)) .where( and( - or( - eq(credentialSetInvitation.email, session.user.email), - isNull(credentialSetInvitation.email) - ), + eq(credentialSetInvitation.email, session.user.email), eq(credentialSetInvitation.status, 'pending'), gt(credentialSetInvitation.expiresAt, new Date()) )