From 22cb73eed93fdf6b0bb6b04f9c688c9162cee3d2 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:47:09 +0100 Subject: [PATCH 1/6] feat(core): Parse individual cookies from cookie header --- packages/astro/src/server/middleware.ts | 5 +- packages/bun/src/integrations/bunserver.ts | 6 +- packages/cloudflare/src/request.ts | 9 +- packages/core/src/utils/request.ts | 60 +++++++++--- packages/core/test/lib/utils/request.test.ts | 91 ++++++++++++++++++- .../common/utils/addHeadersAsAttributes.ts | 4 +- .../http/httpServerSpansIntegration.ts | 5 +- .../runtime/hooks/wrapMiddlewareHandler.ts | 3 +- packages/remix/src/server/instrumentServer.ts | 5 +- .../sveltekit/src/server-common/handle.ts | 6 +- 10 files changed, 169 insertions(+), 25 deletions(-) diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 64fde266a3f8..a12c25ff6045 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -219,7 +219,10 @@ async function instrumentRequestStartHttpServerSpan( // This is here for backwards compatibility, we used to set this here before method, url: stripUrlQueryAndFragment(ctx.url.href), - ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)), + ...httpHeadersToSpanAttributes( + winterCGHeadersToDict(request.headers), + getClient()?.getOptions().sendDefaultPii ?? false, + ), }; if (parametrizedRoute) { diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index 73998e529349..83e7f5ff4967 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -3,6 +3,7 @@ import { captureException, continueTrace, defineIntegration, + getClient, httpHeadersToSpanAttributes, isURLObjectRelative, parseStringToURLObject, @@ -206,7 +207,10 @@ function wrapRequestHandler( routeName = route; } - Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON())); + Object.assign( + attributes, + httpHeadersToSpanAttributes(request.headers.toJSON(), getClient()?.getOptions().sendDefaultPii ?? false), + ); isolationScope.setSDKProcessingMetadata({ normalizedRequest: { diff --git a/packages/cloudflare/src/request.ts b/packages/cloudflare/src/request.ts index 7908d3dcf48e..e8fd48ca7cc8 100644 --- a/packages/cloudflare/src/request.ts +++ b/packages/cloudflare/src/request.ts @@ -3,6 +3,7 @@ import { captureException, continueTrace, flush, + getClient, getHttpSpanDetailsFromUrlObject, httpHeadersToSpanAttributes, parseStringToURLObject, @@ -66,7 +67,13 @@ export function wrapRequestHandler( attributes['user_agent.original'] = userAgentHeader; } - Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers))); + Object.assign( + attributes, + httpHeadersToSpanAttributes( + winterCGHeadersToDict(request.headers), + getClient()?.getOptions().sendDefaultPii ?? false, + ), + ); attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index 1d3985dd8479..10288e121f72 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -1,3 +1,4 @@ +import { getClient } from '../currentScopes'; import type { PolymorphicRequest } from '../types-hoist/polymorphics'; import type { RequestEventData } from '../types-hoist/request'; import type { WebFetchHeaders, WebFetchRequest } from '../types-hoist/webfetchapi'; @@ -128,21 +129,29 @@ function getAbsoluteUrl({ return undefined; } -// "-user" because otherwise it would match "user-agent" const SENSITIVE_HEADER_SNIPPETS = [ 'auth', 'token', 'secret', - 'cookie', - '-user', + 'session', // for the user_session cookie 'password', + 'passwd', + 'pwd', 'key', 'jwt', 'bearer', 'sso', 'saml', + 'crsf', + 'xsrf', + 'credentials', + // Always treat cookie headers as sensitive in case individual key-value cookie pairs cannot properly be extracted + 'set-cookie', + 'cookie', ]; +const PII_HEADER_SNIPPETS = ['x-forwarded-', '-user']; + /** * Converts incoming HTTP request headers to OpenTelemetry span attributes following semantic conventions. * Header names are converted to the format: http.request.header. @@ -152,6 +161,7 @@ const SENSITIVE_HEADER_SNIPPETS = [ */ export function httpHeadersToSpanAttributes( headers: Record, + sendDefaultPii: boolean = false, ): Record { const spanAttributes: Record = {}; @@ -161,16 +171,22 @@ export function httpHeadersToSpanAttributes( return; } - const lowerCasedKey = key.toLowerCase(); - const isSensitive = SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet)); - const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`; + const lowerCasedHeaderKey = key.toLowerCase(); + const isCookieHeader = lowerCasedHeaderKey === 'cookie' || lowerCasedHeaderKey === 'set-cookie'; - if (isSensitive) { - spanAttributes[normalizedKey] = '[Filtered]'; - } else if (Array.isArray(value)) { - spanAttributes[normalizedKey] = value.map(v => (v != null ? String(v) : v)).join(';'); - } else if (typeof value === 'string') { - spanAttributes[normalizedKey] = value; + if (isCookieHeader && typeof value === 'string' && value !== '') { + const cookies = value.split('; '); + + for (const cookie of cookies) { + const [cookieKey, cookieValue] = cookie.split('='); + const lowerCasedCookieKey = String(cookieKey).toLowerCase(); + const normalizedKey = `http.request.header.${normalizeAttributeKey(lowerCasedHeaderKey)}.${normalizeAttributeKey(lowerCasedCookieKey)}`; + + spanAttributes[normalizedKey] = handleHttpHeader(lowerCasedCookieKey, cookieValue, sendDefaultPii); + } + } else { + const normalizedKey = `http.request.header.${normalizeAttributeKey(lowerCasedHeaderKey)}`; + spanAttributes[normalizedKey] = handleHttpHeader(lowerCasedHeaderKey, value, sendDefaultPii); } }); } catch { @@ -180,6 +196,26 @@ export function httpHeadersToSpanAttributes( return spanAttributes; } +function normalizeAttributeKey(key: string): string { + return key.replace(/-/g, '_'); +} + +function handleHttpHeader(lowerCasedKey: string, value: string | string[] | undefined, sendPii: boolean): string { + const isSensitive = sendPii + ? SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet)) + : [...PII_HEADER_SNIPPETS, ...SENSITIVE_HEADER_SNIPPETS].some(snippet => lowerCasedKey.includes(snippet)); + + if (isSensitive) { + return '[Filtered]'; + } else if (Array.isArray(value)) { + return value.map(v => (v != null ? String(v) : v)).join(';'); + } else if (typeof value === 'string') { + return value; + } + + return ''; // Fallback for unexpected types +} + /** Extract the query params from an URL. */ export function extractQueryParamsFromUrl(url: string): string | undefined { // url is path and query string diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index 328aebf29209..c14ccdb1d875 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -612,7 +612,7 @@ describe('request utils', () => { }); }); - describe('PII filtering', () => { + describe('PII/Sensitive data filtering', () => { it('filters sensitive headers case-insensitively', () => { const headers = { AUTHORIZATION: 'Bearer secret-token', @@ -625,12 +625,95 @@ describe('request utils', () => { expect(result).toEqual({ 'http.request.header.content_type': 'application/json', - 'http.request.header.cookie': '[Filtered]', + 'http.request.header.cookie.session': '[Filtered]', 'http.request.header.x_api_key': '[Filtered]', 'http.request.header.authorization': '[Filtered]', }); }); + it('attaches and filters sensitive cookie headers', () => { + const headers = { + Cookie: + 'session=abc123; tracking=enabled; cookie-authentication-key-without-value; theme=dark; lang=en; user_session=xyz789; pref=1', + }; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.cookie.session': '[Filtered]', + 'http.request.header.cookie.tracking': 'enabled', + 'http.request.header.cookie.theme': 'dark', + 'http.request.header.cookie.lang': 'en', + 'http.request.header.cookie.user_session': '[Filtered]', + 'http.request.header.cookie.cookie_authentication_key_without_value': '[Filtered]', + 'http.request.header.cookie.pref': '1', + }); + }); + + it('adds a filtered cookie header when cookie header is present, but has no valid key=value pairs', () => { + const headers1 = { Cookie: ['key', 'val'] }; + const result1 = httpHeadersToSpanAttributes(headers1); + expect(result1).toEqual({ 'http.request.header.cookie': '[Filtered]' }); + + const headers3 = { Cookie: '' }; + const result3 = httpHeadersToSpanAttributes(headers3); + expect(result3).toEqual({ 'http.request.header.cookie': '[Filtered]' }); + }); + + it('attaches and filters sensitive a set-cookie header', () => { + const headers1 = { 'Set-Cookie': 'user_session=def456' }; + const result1 = httpHeadersToSpanAttributes(headers1); + expect(result1).toEqual({ 'http.request.header.set_cookie.user_session': '[Filtered]' }); + + const headers2 = { 'Set-Cookie': 'preferred-color-mode=light' }; + const result2 = httpHeadersToSpanAttributes(headers2); + expect(result2).toEqual({ 'http.request.header.set_cookie.preferred_color_mode': 'light' }); + + const headers3 = { 'Set-Cookie': 'lang=en' }; + const result3 = httpHeadersToSpanAttributes(headers3); + expect(result3).toEqual({ 'http.request.header.set_cookie.lang': 'en' }); + + const headers4 = { 'Set-Cookie': 'timezone=UTC' }; + const result4 = httpHeadersToSpanAttributes(headers4); + expect(result4).toEqual({ 'http.request.header.set_cookie.timezone': 'UTC' }); + }); + + it.each([ + { sendDefaultPii: false, description: 'sendDefaultPii is false (default)' }, + { sendDefaultPii: true, description: 'sendDefaultPii is true' }, + ])('does not include PII headers when $description', ({ sendDefaultPii }) => { + const headers = { + 'Content-Type': 'application/json', + 'User-Agent': 'Mozilla/5.0', + 'x-user': 'my-personal-username', + 'X-Forwarded-For': '192.168.1.1', + 'X-Forwarded-Host': 'example.com', + 'X-Forwarded-Proto': 'https', + }; + + const result = httpHeadersToSpanAttributes(headers, sendDefaultPii); + + if (sendDefaultPii) { + expect(result).toEqual({ + 'http.request.header.content_type': 'application/json', + 'http.request.header.user_agent': 'Mozilla/5.0', + 'http.request.header.x_user': 'my-personal-username', + 'http.request.header.x_forwarded_for': '192.168.1.1', + 'http.request.header.x_forwarded_host': 'example.com', + 'http.request.header.x_forwarded_proto': 'https', + }); + } else { + expect(result).toEqual({ + 'http.request.header.content_type': 'application/json', + 'http.request.header.user_agent': 'Mozilla/5.0', + 'http.request.header.x_user': '[Filtered]', + 'http.request.header.x_forwarded_for': '[Filtered]', + 'http.request.header.x_forwarded_host': '[Filtered]', + 'http.request.header.x_forwarded_proto': '[Filtered]', + }); + } + }); + it('always filters comprehensive list of sensitive headers', () => { const headers = { 'Content-Type': 'application/json', @@ -671,8 +754,8 @@ describe('request utils', () => { 'http.request.header.accept': 'application/json', 'http.request.header.host': 'example.com', 'http.request.header.authorization': '[Filtered]', - 'http.request.header.cookie': '[Filtered]', - 'http.request.header.set_cookie': '[Filtered]', + 'http.request.header.cookie.session': '[Filtered]', + 'http.request.header.set_cookie.session': '[Filtered]', 'http.request.header.x_api_key': '[Filtered]', 'http.request.header.x_auth_token': '[Filtered]', 'http.request.header.x_secret': '[Filtered]', diff --git a/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts b/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts index ff025fc3ecc7..8d4b0eca3724 100644 --- a/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts +++ b/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts @@ -1,5 +1,5 @@ import type { Span, WebFetchHeaders } from '@sentry/core'; -import { httpHeadersToSpanAttributes, winterCGHeadersToDict } from '@sentry/core'; +import { getClient, httpHeadersToSpanAttributes, winterCGHeadersToDict } from '@sentry/core'; /** * Extracts HTTP request headers as span attributes and optionally applies them to a span. @@ -17,7 +17,7 @@ export function addHeadersAsAttributes( ? winterCGHeadersToDict(headers as Headers) : headers; - const headerAttributes = httpHeadersToSpanAttributes(headersDict); + const headerAttributes = httpHeadersToSpanAttributes(headersDict, getClient()?.getOptions().sendDefaultPii ?? false); if (span) { span.setAttributes(headerAttributes); diff --git a/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts b/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts index 34741e95c912..7909482a5923 100644 --- a/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts +++ b/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts @@ -157,7 +157,10 @@ const _httpServerSpansIntegration = ((options: HttpServerSpansIntegrationOptions 'http.flavor': httpVersion, 'net.transport': httpVersion?.toUpperCase() === 'QUIC' ? 'ip_udp' : 'ip_tcp', ...getRequestContentLengthAttribute(request), - ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}), + ...httpHeadersToSpanAttributes( + normalizedRequest.headers || {}, + client.getOptions().sendDefaultPii ?? false, + ), }, }); diff --git a/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts b/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts index 4b41d6e8ab82..3c71c442bb88 100644 --- a/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts +++ b/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts @@ -3,6 +3,7 @@ import { captureException, debug, flushIfServerless, + getClient, httpHeadersToSpanAttributes, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, @@ -172,7 +173,7 @@ function getSpanAttributes( // Get headers from the Node.js request object const headers = event.node?.req?.headers || {}; - const headerAttributes = httpHeadersToSpanAttributes(headers); + const headerAttributes = httpHeadersToSpanAttributes(headers, getClient()?.getOptions().sendDefaultPii ?? false); // Merge header attributes with existing attributes Object.assign(attributes, headerAttributes); diff --git a/packages/remix/src/server/instrumentServer.ts b/packages/remix/src/server/instrumentServer.ts index 2416699cb2a6..fda9b3f10b75 100644 --- a/packages/remix/src/server/instrumentServer.ts +++ b/packages/remix/src/server/instrumentServer.ts @@ -310,7 +310,10 @@ function wrapRequestHandler ServerBuild | Promise [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', method: request.method, - ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)), + ...httpHeadersToSpanAttributes( + winterCGHeadersToDict(request.headers), + clientOptions.sendDefaultPii ?? false, + ), }, }, async span => { diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index 3d9963bd1056..728eb47cf9a1 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -19,6 +19,7 @@ import { winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; +import { getClient } from '@sentry/svelte'; import type { Handle, ResolveOptions } from '@sveltejs/kit'; import { DEBUG_BUILD } from '../common/debug-build'; import { getTracePropagationData, sendErrorToSentry } from './utils'; @@ -204,7 +205,10 @@ async function instrumentHandle( [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', 'http.method': event.request.method, - ...httpHeadersToSpanAttributes(winterCGHeadersToDict(event.request.headers)), + ...httpHeadersToSpanAttributes( + winterCGHeadersToDict(event.request.headers), + getClient()?.getOptions().sendDefaultPii ?? false, + ), }, name: routeName, }, From 27cc608657ee1a47d7f78ca2266b34ce0b4de3fa Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:52:28 +0100 Subject: [PATCH 2/6] check for multiple = sign in cookie value --- packages/core/src/utils/request.ts | 6 +++++- packages/core/test/lib/utils/request.test.ts | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index 10288e121f72..cd074480397a 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -178,7 +178,11 @@ export function httpHeadersToSpanAttributes( const cookies = value.split('; '); for (const cookie of cookies) { - const [cookieKey, cookieValue] = cookie.split('='); + // Split only at the first '=' to preserve '=' characters in cookie values + const equalSignIndex = cookie.indexOf('='); + const cookieKey = equalSignIndex !== -1 ? cookie.substring(0, equalSignIndex) : cookie; + const cookieValue = equalSignIndex !== -1 ? cookie.substring(equalSignIndex + 1) : ''; + const lowerCasedCookieKey = String(cookieKey).toLowerCase(); const normalizedKey = `http.request.header.${normalizeAttributeKey(lowerCasedHeaderKey)}.${normalizeAttributeKey(lowerCasedCookieKey)}`; diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index c14ccdb1d875..fb0da62a5757 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -678,6 +678,12 @@ describe('request utils', () => { expect(result4).toEqual({ 'http.request.header.set_cookie.timezone': 'UTC' }); }); + it('only splits cookies once between key and value, even when more equals signs are present', () => { + const headers = { Cookie: 'random-string=eyJhbGc=.eyJzdWI=.SflKxw' }; + const result = httpHeadersToSpanAttributes(headers); + expect(result).toEqual({ 'http.request.header.cookie.random_string': 'eyJhbGc=.eyJzdWI=.SflKxw' }); + }); + it.each([ { sendDefaultPii: false, description: 'sendDefaultPii is false (default)' }, { sendDefaultPii: true, description: 'sendDefaultPii is true' }, From 5a8f35074197fe82e573f6711d8525227797c944 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:57:31 +0100 Subject: [PATCH 3/6] fix failing tests --- packages/core/src/utils/request.ts | 4 ++-- packages/core/test/lib/utils/request.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index cd074480397a..e5b09387fe0c 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -200,7 +200,7 @@ export function httpHeadersToSpanAttributes( return spanAttributes; } -function normalizeAttributeKey(key: string): string { +function normalizeAttributeKey(key: string): string | undefined { return key.replace(/-/g, '_'); } @@ -217,7 +217,7 @@ function handleHttpHeader(lowerCasedKey: string, value: string | string[] | unde return value; } - return ''; // Fallback for unexpected types + return undefined; // Fallback for unexpected types } /** Extract the query params from an URL. */ diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index fb0da62a5757..377f2d4fb246 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -527,7 +527,7 @@ describe('request utils', () => { 'X-Forwarded-For': '192.168.1.1', }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, true); expect(result).toEqual({ 'http.request.header.host': 'example.com', From e5407f0cc2d3221180c6fc9c1bcb9eff98f33051 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:58:28 +0100 Subject: [PATCH 4/6] fix linting problem --- packages/core/src/utils/request.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index e5b09387fe0c..217b53948a82 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -1,4 +1,3 @@ -import { getClient } from '../currentScopes'; import type { PolymorphicRequest } from '../types-hoist/polymorphics'; import type { RequestEventData } from '../types-hoist/request'; import type { WebFetchHeaders, WebFetchRequest } from '../types-hoist/webfetchapi'; From 1b4d3c5c8698252bef273c33450072fa36bd678f Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:19:16 +0100 Subject: [PATCH 5/6] handle undefined case --- packages/core/src/utils/request.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index 217b53948a82..765049bad5de 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -185,11 +185,18 @@ export function httpHeadersToSpanAttributes( const lowerCasedCookieKey = String(cookieKey).toLowerCase(); const normalizedKey = `http.request.header.${normalizeAttributeKey(lowerCasedHeaderKey)}.${normalizeAttributeKey(lowerCasedCookieKey)}`; - spanAttributes[normalizedKey] = handleHttpHeader(lowerCasedCookieKey, cookieValue, sendDefaultPii); + const headerValue = handleHttpHeader(lowerCasedCookieKey, cookieValue, sendDefaultPii); + if (headerValue !== undefined) { + spanAttributes[normalizedKey] = headerValue; + } } } else { const normalizedKey = `http.request.header.${normalizeAttributeKey(lowerCasedHeaderKey)}`; - spanAttributes[normalizedKey] = handleHttpHeader(lowerCasedHeaderKey, value, sendDefaultPii); + + const headerValue = handleHttpHeader(lowerCasedHeaderKey, value, sendDefaultPii); + if (headerValue !== undefined) { + spanAttributes[normalizedKey] = headerValue; + } } }); } catch { @@ -199,11 +206,15 @@ export function httpHeadersToSpanAttributes( return spanAttributes; } -function normalizeAttributeKey(key: string): string | undefined { +function normalizeAttributeKey(key: string): string { return key.replace(/-/g, '_'); } -function handleHttpHeader(lowerCasedKey: string, value: string | string[] | undefined, sendPii: boolean): string { +function handleHttpHeader( + lowerCasedKey: string, + value: string | string[] | undefined, + sendPii: boolean, +): string | undefined { const isSensitive = sendPii ? SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet)) : [...PII_HEADER_SNIPPETS, ...SENSITIVE_HEADER_SNIPPETS].some(snippet => lowerCasedKey.includes(snippet)); @@ -216,7 +227,7 @@ function handleHttpHeader(lowerCasedKey: string, value: string | string[] | unde return value; } - return undefined; // Fallback for unexpected types + return undefined; } /** Extract the query params from an URL. */ From f3231a38697e7fe4c5fe96c8335f7e01b896a259 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:20:09 +0100 Subject: [PATCH 6/6] handle undefined type case --- packages/core/src/utils/request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index 765049bad5de..1301d301aef1 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -182,7 +182,7 @@ export function httpHeadersToSpanAttributes( const cookieKey = equalSignIndex !== -1 ? cookie.substring(0, equalSignIndex) : cookie; const cookieValue = equalSignIndex !== -1 ? cookie.substring(equalSignIndex + 1) : ''; - const lowerCasedCookieKey = String(cookieKey).toLowerCase(); + const lowerCasedCookieKey = cookieKey.toLowerCase(); const normalizedKey = `http.request.header.${normalizeAttributeKey(lowerCasedHeaderKey)}.${normalizeAttributeKey(lowerCasedCookieKey)}`; const headerValue = handleHttpHeader(lowerCasedCookieKey, cookieValue, sendDefaultPii);