From f5d7a4359a1ccff6a83d5bc157a1e2c409d63559 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 6 Mar 2026 08:19:50 -0600 Subject: [PATCH 1/3] fix(express): add empty path fallback for frontendApiProxy When `frontendApiProxy.path` is set to '/', `stripTrailingSlashes` returns an empty string, causing every request to be intercepted as a proxy request. Add `|| DEFAULT_PROXY_PATH` fallback to match the existing guard in the hono and fastify packages. --- packages/express/src/authenticateRequest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/express/src/authenticateRequest.ts b/packages/express/src/authenticateRequest.ts index 42842f00700..4002d82a821 100644 --- a/packages/express/src/authenticateRequest.ts +++ b/packages/express/src/authenticateRequest.ts @@ -103,7 +103,7 @@ export const authenticateAndDecorateRequest = (options: ClerkMiddlewareOptions = // Extract proxy configuration const frontendApiProxy = options.frontendApiProxy; - const proxyPath = stripTrailingSlashes(frontendApiProxy?.path ?? DEFAULT_PROXY_PATH); + const proxyPath = stripTrailingSlashes(frontendApiProxy?.path ?? DEFAULT_PROXY_PATH) || DEFAULT_PROXY_PATH; // eslint-disable-next-line @typescript-eslint/no-misused-promises const middleware: RequestHandler = async (request, response, next) => { From 98161b0cf475836ded1ce3e81b67550a230a85c7 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 6 Mar 2026 10:07:46 -0600 Subject: [PATCH 2/3] chore: add changeset --- .changeset/fix-express-proxy-path-guard.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-express-proxy-path-guard.md diff --git a/.changeset/fix-express-proxy-path-guard.md b/.changeset/fix-express-proxy-path-guard.md new file mode 100644 index 00000000000..fa6d4433bc3 --- /dev/null +++ b/.changeset/fix-express-proxy-path-guard.md @@ -0,0 +1,5 @@ +--- +'@clerk/express': patch +--- + +Fix empty path fallback for `frontendApiProxy` to prevent intercepting all requests when `path` resolves to an empty string From 08c1d22f1faed1d37291f80396cc42112266cdc8 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 6 Mar 2026 10:11:04 -0600 Subject: [PATCH 3/3] test(express): add tests for empty proxy path fallback --- .../src/__tests__/clerkMiddleware.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/express/src/__tests__/clerkMiddleware.test.ts b/packages/express/src/__tests__/clerkMiddleware.test.ts index 9012939cbb4..c2c6092d67a 100644 --- a/packages/express/src/__tests__/clerkMiddleware.test.ts +++ b/packages/express/src/__tests__/clerkMiddleware.test.ts @@ -171,6 +171,35 @@ describe('clerkMiddleware', () => { expect(response.header).toHaveProperty('x-clerk-auth-status', 'handshake'); }); + it('falls back to default proxy path when path reduces to empty string', async () => { + mockClerkFrontendApiProxy.mockResolvedValueOnce(new globalThis.Response('proxied', { status: 200 })); + + // path: '/' strips to '' — should fall back to DEFAULT_PROXY_PATH (/__clerk) + // and only intercept /__clerk, not every request + await runMiddlewareOnPath( + clerkMiddleware({ frontendApiProxy: { enabled: true, path: '/' } }), + '/__clerk/v1/client', + {}, + ).expect(200); + + expect(mockClerkFrontendApiProxy).toHaveBeenCalled(); + }); + + it('does not intercept non-proxy paths when path reduces to empty string', async () => { + // path: '/' strips to '' — without the fallback guard, this would match everything + const response = await runMiddlewareOnPath( + clerkMiddleware({ frontendApiProxy: { enabled: true, path: '/' } }), + '/api/users', + { + Cookie: '__client_uat=1711618859;', + 'Sec-Fetch-Dest': 'document', + }, + ).expect(307); + + expect(response.header).toHaveProperty('x-clerk-auth-status', 'handshake'); + expect(mockClerkFrontendApiProxy).not.toHaveBeenCalled(); + }); + it('still authenticates requests to other paths when proxy is configured', async () => { const response = await runMiddlewareOnPath( clerkMiddleware({ frontendApiProxy: { enabled: true } }),