From 9d6708bbeebc97dc9d933650ad4a8de725179b4f Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Fri, 22 May 2026 15:56:26 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20upload=20proxy=20public=20?= =?UTF-8?q?URL=20(#2508)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Prevent signed upload proxy URLs from using internal request origins in self-hosted reverse-proxy setups. - Resolve runtime upload proxy URLs from `NEXT_PUBLIC_VIEWER_URL` and builder upload proxy URLs from `NEXTAUTH_URL`. - Add regression coverage for internal container origins like `https://2e862faf612f:3000`. --- .../features/upload/api/generateUploadUrl.ts | 7 +++- .../src/api/getUploadProxyBaseUrl.ts | 7 +++- .../src/s3/resolveUploadProxyBaseUrl.test.ts | 36 +++++++++++++++++++ .../lib/src/s3/resolveUploadProxyBaseUrl.ts | 26 ++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 packages/lib/src/s3/resolveUploadProxyBaseUrl.test.ts create mode 100644 packages/lib/src/s3/resolveUploadProxyBaseUrl.ts diff --git a/apps/builder/src/features/upload/api/generateUploadUrl.ts b/apps/builder/src/features/upload/api/generateUploadUrl.ts index 9a584e69f..02bd3635e 100644 --- a/apps/builder/src/features/upload/api/generateUploadUrl.ts +++ b/apps/builder/src/features/upload/api/generateUploadUrl.ts @@ -7,6 +7,7 @@ import { parseUploadPathSegment, resolveUploadFileType, } from "@typebot.io/lib/s3/createUploadFilePath"; +import { resolveUploadProxyBaseUrl } from "@typebot.io/lib/s3/resolveUploadProxyBaseUrl"; import { generateSignedUploadProxyUrl } from "@typebot.io/lib/s3/signedUploadProxy"; import prisma from "@typebot.io/prisma"; import { z } from "zod"; @@ -79,7 +80,11 @@ export const generateUploadUrl = authenticatedProcedure }); return generateSignedUploadProxyUrl({ - baseUrl: apiOrigin ?? env.NEXTAUTH_URL, + baseUrl: resolveUploadProxyBaseUrl({ + publicBaseUrls: [env.NEXTAUTH_URL], + fallbackBaseUrl: env.NEXTAUTH_URL, + requestOrigin: apiOrigin, + }), fileType: resolvedFileType, filePath, }); diff --git a/packages/blocks/fileInput/src/api/getUploadProxyBaseUrl.ts b/packages/blocks/fileInput/src/api/getUploadProxyBaseUrl.ts index 18da825a4..4afc4d1e2 100644 --- a/packages/blocks/fileInput/src/api/getUploadProxyBaseUrl.ts +++ b/packages/blocks/fileInput/src/api/getUploadProxyBaseUrl.ts @@ -1,4 +1,9 @@ import { env } from "@typebot.io/env"; +import { resolveUploadProxyBaseUrl } from "@typebot.io/lib/s3/resolveUploadProxyBaseUrl"; export const getUploadProxyBaseUrl = (apiOrigin: string | undefined) => - apiOrigin ?? env.NEXT_PUBLIC_VIEWER_URL[0] ?? env.NEXTAUTH_URL; + resolveUploadProxyBaseUrl({ + publicBaseUrls: env.NEXT_PUBLIC_VIEWER_URL, + fallbackBaseUrl: env.NEXTAUTH_URL, + requestOrigin: apiOrigin, + }); diff --git a/packages/lib/src/s3/resolveUploadProxyBaseUrl.test.ts b/packages/lib/src/s3/resolveUploadProxyBaseUrl.test.ts new file mode 100644 index 000000000..f66934569 --- /dev/null +++ b/packages/lib/src/s3/resolveUploadProxyBaseUrl.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "bun:test"; +import { resolveUploadProxyBaseUrl } from "./resolveUploadProxyBaseUrl"; + +describe("resolveUploadProxyBaseUrl", () => { + it("uses the matching configured public URL when the request origin is public", () => { + expect( + resolveUploadProxyBaseUrl({ + publicBaseUrls: [ + "https://viewer.example.com", + "https://bot.example.com", + ], + fallbackBaseUrl: "https://builder.example.com", + requestOrigin: "https://bot.example.com", + }), + ).toBe("https://bot.example.com"); + }); + + it("ignores an internal request origin and uses the first configured public URL", () => { + expect( + resolveUploadProxyBaseUrl({ + publicBaseUrls: ["https://bot.example.com"], + fallbackBaseUrl: "https://builder.example.com", + requestOrigin: "https://2e862faf612f:3000", + }), + ).toBe("https://bot.example.com"); + }); + + it("falls back when no configured public URL is available", () => { + expect( + resolveUploadProxyBaseUrl({ + publicBaseUrls: [], + fallbackBaseUrl: "https://builder.example.com", + }), + ).toBe("https://builder.example.com"); + }); +}); diff --git a/packages/lib/src/s3/resolveUploadProxyBaseUrl.ts b/packages/lib/src/s3/resolveUploadProxyBaseUrl.ts new file mode 100644 index 000000000..ca268c2bd --- /dev/null +++ b/packages/lib/src/s3/resolveUploadProxyBaseUrl.ts @@ -0,0 +1,26 @@ +type ResolveUploadProxyBaseUrlProps = { + publicBaseUrls: string[]; + fallbackBaseUrl: string; + requestOrigin?: string; +}; + +export const resolveUploadProxyBaseUrl = ({ + publicBaseUrls, + fallbackBaseUrl, + requestOrigin, +}: ResolveUploadProxyBaseUrlProps) => + (requestOrigin + ? publicBaseUrls.find( + (publicBaseUrl) => parseOrigin(publicBaseUrl) === requestOrigin, + ) + : undefined) ?? + publicBaseUrls[0] ?? + fallbackBaseUrl; + +const parseOrigin = (url: string) => { + try { + return new URL(url).origin; + } catch { + return; + } +};