From 7d40bc93584248de8cf4c0131ddd7d298fe3b359 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 4 Jul 2026 16:37:12 -0700 Subject: [PATCH] fix(mcp): correct fetchFn fallback order in mcpAuthGuarded Spread order previously let an explicit fetchFn (including fetchFn: undefined) in options silently disable the SSRF-guarded default. Fallback is now applied after the spread so the guard always wins unless a real override is passed. fix(tools): handle non-numeric Drive file size in early size check Guard the pre-download size check against a malformed metadata.size string so it's skipped explicitly instead of relying on an incidental NaN no-op; the streaming cap on the actual download still enforces the limit either way. --- .../tools/google_drive/download/route.test.ts | 24 +++++++++++++++++++ .../api/tools/google_drive/download/route.ts | 9 ++++--- apps/sim/lib/mcp/oauth/auth.test.ts | 13 ++++++++++ apps/sim/lib/mcp/oauth/auth.ts | 2 +- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/apps/sim/app/api/tools/google_drive/download/route.test.ts b/apps/sim/app/api/tools/google_drive/download/route.test.ts index 2599b515a3b..a9ec28346de 100644 --- a/apps/sim/app/api/tools/google_drive/download/route.test.ts +++ b/apps/sim/app/api/tools/google_drive/download/route.test.ts @@ -133,6 +133,30 @@ describe('POST /api/tools/google_drive/download', () => { expect(data.success).toBe(false) }) + it('proceeds to the streamed download when metadata size is malformed', async () => { + mockSecureFetchWithPinnedIP + .mockResolvedValueOnce( + jsonResponse({ + id: 'file-abc', + name: 'report.pdf', + mimeType: 'application/pdf', + size: 'not-a-number', + capabilities: { canReadRevisions: false }, + }) + ) + .mockResolvedValueOnce(fileResponse(1024)) + + const response = await POST(createMockRequest('POST', baseBody)) + expect(response.status).toBe(200) + const data = (await response.json()) as { success: boolean; output: { file: { size: number } } } + expect(data.success).toBe(true) + expect(data.output.file.size).toBe(1024) + + // The early size check should be skipped, but the streaming cap must still apply. + const downloadCall = mockSecureFetchWithPinnedIP.mock.calls[1] + expect(downloadCall[2]).toMatchObject({ maxResponseBytes: MAX_FILE_SIZE }) + }) + it('does not require a metadata size for Google Workspace exports', async () => { mockSecureFetchWithPinnedIP .mockResolvedValueOnce( diff --git a/apps/sim/app/api/tools/google_drive/download/route.ts b/apps/sim/app/api/tools/google_drive/download/route.ts index a4e76f2acd6..788a7de418b 100644 --- a/apps/sim/app/api/tools/google_drive/download/route.ts +++ b/apps/sim/app/api/tools/google_drive/download/route.ts @@ -188,11 +188,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => { logger.info(`[${requestId}] Downloading regular file`, { fileId, mimeType: fileMimeType }) if (metadata.size) { - assertKnownSizeWithinLimit( - Number.parseInt(metadata.size, 10), - MAX_FILE_SIZE, - `Google Drive file ${fileId}` - ) + const parsedSize = Number.parseInt(metadata.size, 10) + if (Number.isFinite(parsedSize)) { + assertKnownSizeWithinLimit(parsedSize, MAX_FILE_SIZE, `Google Drive file ${fileId}`) + } } const downloadUrl = `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media&supportsAllDrives=true` diff --git a/apps/sim/lib/mcp/oauth/auth.test.ts b/apps/sim/lib/mcp/oauth/auth.test.ts index 8b8df25dfdd..d9b6e79c492 100644 --- a/apps/sim/lib/mcp/oauth/auth.test.ts +++ b/apps/sim/lib/mcp/oauth/auth.test.ts @@ -51,4 +51,17 @@ describe('mcpAuthGuarded', () => { fetchFn: overrideFetch, }) }) + + it('falls back to the SSRF-guarded fetch when fetchFn is explicitly undefined', async () => { + await mcpAuthGuarded(provider, { + serverUrl: 'https://mcp.example.com/mcp', + fetchFn: undefined, + }) + + expect(mockCreateSsrfGuardedMcpFetch).toHaveBeenCalledTimes(1) + expect(mockAuth).toHaveBeenCalledWith(provider, { + serverUrl: 'https://mcp.example.com/mcp', + fetchFn: mockGuardedFetch, + }) + }) }) diff --git a/apps/sim/lib/mcp/oauth/auth.ts b/apps/sim/lib/mcp/oauth/auth.ts index 68d9ff7f1fb..e94a91b058a 100644 --- a/apps/sim/lib/mcp/oauth/auth.ts +++ b/apps/sim/lib/mcp/oauth/auth.ts @@ -15,5 +15,5 @@ export function mcpAuthGuarded( provider: OAuthClientProvider, options: McpAuthOptions ): ReturnType { - return auth(provider, { fetchFn: createSsrfGuardedMcpFetch(), ...options }) + return auth(provider, { ...options, fetchFn: options.fetchFn ?? createSsrfGuardedMcpFetch() }) }