From 0d8ebc8cbc4f3f963975e3f168105333a208cb43 Mon Sep 17 00:00:00 2001 From: waleed Date: Sun, 14 Jun 2026 18:47:20 -0700 Subject: [PATCH] fix(chat): fail closed when embed gate cannot resolve workspace --- apps/sim/app/api/chat/utils.test.ts | 16 ++++++++++++++++ apps/sim/app/api/chat/utils.ts | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/api/chat/utils.test.ts b/apps/sim/app/api/chat/utils.test.ts index d592060eac..539ba6c6e0 100644 --- a/apps/sim/app/api/chat/utils.test.ts +++ b/apps/sim/app/api/chat/utils.test.ts @@ -4,6 +4,8 @@ * @vitest-environment node */ import { + dbChainMock, + dbChainMockFns, encryptionMock, encryptionMockFns, loggingSessionMock, @@ -34,6 +36,8 @@ const { flagState: { isBillingEnabled: false, isFreeApiDeploymentGateEnabled: true }, })) +vi.mock('@sim/db', () => dbChainMock) + vi.mock('@/lib/billing/core/api-access', () => ({ isWorkspaceApiExecutionEntitled: mockIsWorkspaceApiExecutionEntitled, })) @@ -480,6 +484,7 @@ describe('assertChatEmbedAllowed', () => { flagState.isBillingEnabled = true flagState.isFreeApiDeploymentGateEnabled = true mockIsWorkspaceApiExecutionEntitled.mockResolvedValue(true) + dbChainMockFns.limit.mockResolvedValue([{ workspaceId: 'ws-1' }]) }) it('returns 403 for a cross-site origin when the owner is on the free plan', async () => { @@ -501,6 +506,17 @@ describe('assertChatEmbedAllowed', () => { expect(res).toBeNull() }) + it('returns 403 for a cross-site origin when the workflow has no active workspace', async () => { + dbChainMockFns.limit.mockResolvedValueOnce([]) + const res = await assertChatEmbedAllowed( + chatRequest('https://evil.example.com'), + 'wf-1', + 'req-1' + ) + expect(res?.status).toBe(403) + expect(mockIsWorkspaceApiExecutionEntitled).not.toHaveBeenCalled() + }) + it('allows a first-party *.sim.ai origin without gating', async () => { const res = await assertChatEmbedAllowed(chatRequest('https://chat.sim.ai'), 'wf-1', 'req-1') expect(res).toBeNull() diff --git a/apps/sim/app/api/chat/utils.ts b/apps/sim/app/api/chat/utils.ts index a72b5c8f5d..6d01d6fa67 100644 --- a/apps/sim/app/api/chat/utils.ts +++ b/apps/sim/app/api/chat/utils.ts @@ -81,7 +81,14 @@ export async function assertChatEmbedAllowed( .where(and(eq(workflow.id, workflowId), isNull(workflow.archivedAt))) .limit(1) - if (!(await isWorkspaceApiExecutionEntitled(wf?.workspaceId ?? undefined))) { + if (!wf?.workspaceId) { + logger.warn( + `[${requestId}] Chat embed blocked: no active workspace for workflow ${workflowId}, origin=${origin}` + ) + return createErrorResponse('This chat is currently unavailable', 403) + } + + if (!(await isWorkspaceApiExecutionEntitled(wf.workspaceId))) { logger.warn(`[${requestId}] Chat embed blocked: workspace on free plan, origin=${origin}`) return createErrorResponse('Embedding this chat on external sites requires a paid plan', 403) }