From 104200dc662fda971f5e39568ec03daa9a93a7c9 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 18 Jun 2026 14:26:56 -0400 Subject: [PATCH] fix(core): Prevent outgoing HTTP instrumentation from crashing on `//` request paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The outgoing-request instrumentation builds a URL from the request path via `new URL(path, base)`. A path starting with `//` (valid for e.g. S3 object keys like `//Trust scores/test.html`) is parsed as a protocol-relative reference, discarding the request origin and treating the path as the authority — which throws `TypeError: Invalid URL` for authorities the parser rejects. Because this runs synchronously inside `diagnostics_channel.publish()` during the `ClientRequest` constructor, the throw becomes an uncaught exception that crashes the host process (crash-looping in the reported case). Resolve `//`-prefixed paths against the request origin as absolute paths so they produce the correct URL, and wrap `getRequestUrl` in a try/catch returning `''` so the instrumentation can never take down the application. Fixes #21627 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/integrations/http/get-request-url.ts | 11 ++++++++-- .../integrations/http/get-request-url.test.ts | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/core/src/integrations/http/get-request-url.ts b/packages/core/src/integrations/http/get-request-url.ts index 024ac704acd1..3de4d99cf339 100644 --- a/packages/core/src/integrations/http/get-request-url.ts +++ b/packages/core/src/integrations/http/get-request-url.ts @@ -23,7 +23,11 @@ export function getRequestOptions(request: HttpClientRequest): HttpRequestOption } export function getRequestUrl(requestOptions: HttpRequestOptions): string { - return String(getRequestUrlObject(requestOptions)); + try { + return String(getRequestUrlObject(requestOptions)); + } catch { + return ''; + } } export function getRequestUrlObject(requestOptions: HttpRequestOptions): URL { @@ -37,7 +41,10 @@ export function getRequestUrlObject(requestOptions: HttpRequestOptions): URL { ? '' : `:${requestOptions.port}`; const path = requestOptions.path ? requestOptions.path : '/'; - return new URL(path, `${protocol}//${hostname}${port}`); + const base = `${protocol}//${hostname}${port}`; + // A path starting with `//` (valid for e.g. S3 object keys) would otherwise be parsed as a + // protocol-relative reference, discarding the request's origin, so we resolve it against the origin. + return new URL(path.startsWith('//') ? `${base}${path}` : path, base); } /** diff --git a/packages/core/test/lib/integrations/http/get-request-url.test.ts b/packages/core/test/lib/integrations/http/get-request-url.test.ts index 55bc1ba90f3a..92c79515f210 100644 --- a/packages/core/test/lib/integrations/http/get-request-url.test.ts +++ b/packages/core/test/lib/integrations/http/get-request-url.test.ts @@ -43,6 +43,21 @@ describe('getRequestUrl', () => { }, 'data:text/plain;hello, world!', ], + // Paths starting with `//` are valid (e.g. S3 object keys) but would otherwise be + // parsed as protocol-relative URLs. See #21627. + [ + { protocol: 'https:', hostname: 'my-bucket.s3.us-east-1.amazonaws.com', port: 443, path: '//test.html' }, + 'https://my-bucket.s3.us-east-1.amazonaws.com//test.html', + ], + [ + { + protocol: 'https:', + hostname: 'my-bucket.s3.us-east-1.amazonaws.com', + port: 443, + path: '//Trust%20scores/test.html', + }, + 'https://my-bucket.s3.us-east-1.amazonaws.com//Trust%20scores/test.html', + ], ])('works with %s', (input: HttpRequestOptions, expected: string | undefined) => { // pretend to be a client request that option-ifies to this value const clientRequest = { @@ -55,4 +70,10 @@ describe('getRequestUrl', () => { expect(String(getRequestUrl(input))).toBe(expected); expect(getRequestUrlFromClientRequest(clientRequest)).toBe(expected); }); + + it('does not throw for unparseable request options, returning an empty string', () => { + const input = { protocol: 'http:', hostname: '', port: 80, path: '//%' } as HttpRequestOptions; + expect(() => getRequestUrl(input)).not.toThrow(); + expect(getRequestUrl(input)).toBe(''); + }); });