From ab4a62f7802c6c8d9bb7646f5d99bb14bd506f16 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 23 Sep 2026 09:38:49 +0200 Subject: [PATCH] fix(v10/tanstackstart-react): Reject non-POST requests to the managed tunnel route Backport of: #24615 --- .../tanstackstart-react/tests/tunnel.test.ts | 9 +++++++++ packages/tanstackstart-react/src/vite/tunnelRoute.ts | 4 ++++ .../tanstackstart-react/test/vite/tunnelRoute.test.ts | 9 +++++++++ 3 files changed, 22 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/tunnel.test.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/tunnel.test.ts index d3f9694a6d22..2c609065912d 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/tunnel.test.ts +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/tunnel.test.ts @@ -62,6 +62,15 @@ test('Sends client-side errors through the configured tunnel route', async ({ pa expect(errorEvent.transaction).toBe('/'); }); +test('Rejects non-POST requests to the managed tunnel route instead of rendering the app', async ({ request }) => { + test.skip(tunnelRouteMode !== 'static', 'Requires a known managed tunnel path'); + + const response = await request.get('/monitor'); + + expect(response.status()).toBe(405); + expect(response.headers()['allow']).toBe('POST'); +}); + function pathnameMatchesTunnelRoute(pathname: string): boolean { return typeof expectedTunnelPathMatcher === 'string' ? pathname === expectedTunnelPathMatcher diff --git a/packages/tanstackstart-react/src/vite/tunnelRoute.ts b/packages/tanstackstart-react/src/vite/tunnelRoute.ts index c7af98412461..06f3337b4e5e 100644 --- a/packages/tanstackstart-react/src/vite/tunnelRoute.ts +++ b/packages/tanstackstart-react/src/vite/tunnelRoute.ts @@ -194,6 +194,10 @@ export const Route = createFileRoute(${serializedTunnelRoute})({ const Sentry = await import('@sentry/tanstackstart-react'); return Sentry.createSentryTunnelRoute(${serializedAllowedDsns ? `{ allowedDsns: ${serializedAllowedDsns} }` : `{}`}).handlers.POST({ request }); }, + // Without a handler for a method, TanStack Start falls back to SSR-rendering the app (running root loaders). + ANY() { + return new Response(null, { status: 405, headers: { Allow: 'POST' } }); + }, }, }, }); diff --git a/packages/tanstackstart-react/test/vite/tunnelRoute.test.ts b/packages/tanstackstart-react/test/vite/tunnelRoute.test.ts index 822a01aeeeff..22b3a9be56a5 100644 --- a/packages/tanstackstart-react/test/vite/tunnelRoute.test.ts +++ b/packages/tanstackstart-react/test/vite/tunnelRoute.test.ts @@ -139,6 +139,15 @@ describe('tunnelRoute vite plugin', () => { expect(virtualRouteModule).toContain('createSentryTunnelRoute({})'); }); + it('rejects non-POST requests in the virtual managed tunnel route module', async () => { + const plugin = makeTunnelRoutePlugin('/monitor'); + + const virtualRouteModule = plugin.load && (await plugin.load('\0virtual:sentry-tanstackstart-react/tunnel-route')); + + expect(virtualRouteModule).toContain('ANY()'); + expect(virtualRouteModule).toContain("status: 405, headers: { Allow: 'POST' }"); + }); + it('treats an empty string `path` like omitted and uses a generated tunnel route', () => { const plugin = makeTunnelRoutePlugin({ path: '' });