From a7304ce626cf827a126c0a72cbfd4cb09ece7b3a Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:01:58 +0100 Subject: [PATCH 1/4] fix(core): Always include sensitive headers and redact content regardless of `sendDefaultPii` --- packages/astro/src/server/middleware.ts | 5 +- packages/bun/src/integrations/bunserver.ts | 3 +- packages/cloudflare/src/request.ts | 3 +- packages/core/src/utils/request.ts | 39 ++++++---- packages/core/test/lib/utils/request.test.ts | 76 ++++++++----------- .../common/utils/addHeadersAsAttributes.ts | 2 +- 6 files changed, 62 insertions(+), 66 deletions(-) diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index a12c25ff6045..64fde266a3f8 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -219,10 +219,7 @@ 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), - getClient()?.getOptions().sendDefaultPii ?? false, - ), + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)), }; if (parametrizedRoute) { diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index 4a079f488474..0c88331b5175 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -208,8 +208,7 @@ function wrapRequestHandler( } const client = getClient(); - const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; - Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), sendDefaultPii)); + Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON())); isolationScope.setSDKProcessingMetadata({ normalizedRequest: { diff --git a/packages/cloudflare/src/request.ts b/packages/cloudflare/src/request.ts index 5c97562d9fde..7908d3dcf48e 100644 --- a/packages/cloudflare/src/request.ts +++ b/packages/cloudflare/src/request.ts @@ -66,8 +66,7 @@ export function wrapRequestHandler( attributes['user_agent.original'] = userAgentHeader; } - const sendDefaultPii = options.sendDefaultPii ?? false; - Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), sendDefaultPii)); + Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers))); attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index ffd60f3e8486..1d3985dd8479 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -129,7 +129,19 @@ function getAbsoluteUrl({ } // "-user" because otherwise it would match "user-agent" -const SENSITIVE_HEADER_SNIPPETS = ['auth', 'token', 'secret', 'cookie', '-user', 'password', 'key']; +const SENSITIVE_HEADER_SNIPPETS = [ + 'auth', + 'token', + 'secret', + 'cookie', + '-user', + 'password', + 'key', + 'jwt', + 'bearer', + 'sso', + 'saml', +]; /** * Converts incoming HTTP request headers to OpenTelemetry span attributes following semantic conventions. @@ -140,26 +152,25 @@ const SENSITIVE_HEADER_SNIPPETS = ['auth', 'token', 'secret', 'cookie', '-user', */ export function httpHeadersToSpanAttributes( headers: Record, - sendDefaultPii: boolean = false, ): Record { const spanAttributes: Record = {}; try { Object.entries(headers).forEach(([key, value]) => { - if (value !== undefined) { - const lowerCasedKey = key.toLowerCase(); - - if (!sendDefaultPii && SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet))) { - return; - } + if (value == null) { + return; + } - const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`; + const lowerCasedKey = key.toLowerCase(); + const isSensitive = SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet)); + const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`; - if (Array.isArray(value)) { - spanAttributes[normalizedKey] = value.map(v => (v !== null && v !== undefined ? String(v) : v)).join(';'); - } else if (typeof value === 'string') { - spanAttributes[normalizedKey] = value; - } + 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; } }); } catch { diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index b37ee860f43f..e0cf231205fd 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -613,61 +613,25 @@ describe('request utils', () => { }); describe('PII filtering', () => { - it('filters out sensitive headers when sendDefaultPii is false (default)', () => { - const headers = { - 'Content-Type': 'application/json', - 'User-Agent': 'test-agent', - Authorization: 'Bearer secret-token', - Cookie: 'session=abc123', - 'X-API-Key': 'api-key-123', - 'X-Auth-Token': 'auth-token-456', - }; - - const result = httpHeadersToSpanAttributes(headers, false); - - expect(result).toEqual({ - 'http.request.header.content_type': 'application/json', - 'http.request.header.user_agent': 'test-agent', - // Sensitive headers should be filtered out - }); - }); - - it('includes sensitive headers when sendDefaultPii is true', () => { - const headers = { - 'Content-Type': 'application/json', - 'User-Agent': 'test-agent', - Authorization: 'Bearer secret-token', - Cookie: 'session=abc123', - 'X-API-Key': 'api-key-123', - }; - - const result = httpHeadersToSpanAttributes(headers, true); - - expect(result).toEqual({ - 'http.request.header.content_type': 'application/json', - 'http.request.header.user_agent': 'test-agent', - 'http.request.header.authorization': 'Bearer secret-token', - 'http.request.header.cookie': 'session=abc123', - 'http.request.header.x_api_key': 'api-key-123', - }); - }); - it('filters sensitive headers case-insensitively', () => { const headers = { AUTHORIZATION: 'Bearer secret-token', Cookie: 'session=abc123', - 'x-api-key': 'key-123', + 'x-aPi-kEy': 'key-123', 'Content-Type': 'application/json', }; - const result = httpHeadersToSpanAttributes(headers, false); + const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ 'http.request.header.content_type': 'application/json', + 'http.request.header.cookie': '[Filtered]', + 'http.request.header.x_api_key': '[Filtered]', + 'http.request.header.authorization': '[Filtered]', }); }); - it('filters comprehensive list of sensitive headers', () => { + it('filters comprehensive list of sensitive headers when $description', () => { const headers = { 'Content-Type': 'application/json', 'User-Agent': 'test-agent', @@ -692,15 +656,41 @@ describe('request utils', () => { 'X-Private-Key': 'private', 'X-Forwarded-user': 'user', 'X-Forwarded-authorization': 'auth', + 'x-jwt-token': 'jwt', + 'x-bearer-token': 'bearer', + 'x-sso-token': 'sso', + 'x-saml-token': 'saml', }; - const result = httpHeadersToSpanAttributes(headers, false); + const result = httpHeadersToSpanAttributes(headers); + // Sensitive headers are always included and redacted expect(result).toEqual({ 'http.request.header.content_type': 'application/json', 'http.request.header.user_agent': 'test-agent', '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.x_api_key': '[Filtered]', + 'http.request.header.x_auth_token': '[Filtered]', + 'http.request.header.x_secret': '[Filtered]', + 'http.request.header.x_secret_key': '[Filtered]', + 'http.request.header.www_authenticate': '[Filtered]', + 'http.request.header.proxy_authorization': '[Filtered]', + 'http.request.header.x_access_token': '[Filtered]', + 'http.request.header.x_csrf_token': '[Filtered]', + 'http.request.header.x_xsrf_token': '[Filtered]', + 'http.request.header.x_session_token': '[Filtered]', + 'http.request.header.x_password': '[Filtered]', + 'http.request.header.x_private_key': '[Filtered]', + 'http.request.header.x_forwarded_user': '[Filtered]', + 'http.request.header.x_forwarded_authorization': '[Filtered]', + 'http.request.header.x_jwt_token': '[Filtered]', + 'http.request.header.x_bearer_token': '[Filtered]', + 'http.request.header.x_sso_token': '[Filtered]', + 'http.request.header.x_saml_token': '[Filtered]', }); }); }); diff --git a/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts b/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts index 4e8cdb3fe7c9..35abc4077ec6 100644 --- a/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts +++ b/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts @@ -20,7 +20,7 @@ export function addHeadersAsAttributes( ? winterCGHeadersToDict(headers as Headers) : headers; - const headerAttributes = httpHeadersToSpanAttributes(headersDict, sendDefaultPii); + const headerAttributes = httpHeadersToSpanAttributes(headersDict); if (span) { span.setAttributes(headerAttributes); From 837994685a38a0ca7e0e91486069c45e08b2aafb Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:42:49 +0100 Subject: [PATCH 2/4] remove second parameter --- .../integrations/http/httpServerSpansIntegration.ts | 3 +-- .../nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts | 7 +------ packages/remix/src/server/instrumentServer.ts | 5 +---- packages/sveltekit/src/server-common/handle.ts | 11 ++--------- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts b/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts index c24c0c68d1da..34741e95c912 100644 --- a/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts +++ b/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts @@ -136,7 +136,6 @@ const _httpServerSpansIntegration = ((options: HttpServerSpansIntegrationOptions const method = normalizedRequest.method || request.method?.toUpperCase() || 'GET'; const httpTargetWithoutQueryFragment = urlObj ? urlObj.pathname : stripUrlQueryAndFragment(fullUrl); const bestEffortTransactionName = `${method} ${httpTargetWithoutQueryFragment}`; - const shouldSendDefaultPii = client.getOptions().sendDefaultPii ?? false; // We use the plain tracer.startSpan here so we can pass the span kind const span = tracer.startSpan(bestEffortTransactionName, { @@ -158,7 +157,7 @@ const _httpServerSpansIntegration = ((options: HttpServerSpansIntegrationOptions 'http.flavor': httpVersion, 'net.transport': httpVersion?.toUpperCase() === 'QUIC' ? 'ip_udp' : 'ip_tcp', ...getRequestContentLengthAttribute(request), - ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, shouldSendDefaultPii), + ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}), }, }); diff --git a/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts b/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts index a04b866cd774..4b41d6e8ab82 100644 --- a/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts +++ b/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts @@ -3,7 +3,6 @@ import { captureException, debug, flushIfServerless, - getClient, httpHeadersToSpanAttributes, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, @@ -171,13 +170,9 @@ function getSpanAttributes( attributes['http.route'] = event.path; } - // Extract and add HTTP headers as span attributes - const client = getClient(); - const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; - // Get headers from the Node.js request object const headers = event.node?.req?.headers || {}; - const headerAttributes = httpHeadersToSpanAttributes(headers, sendDefaultPii); + const headerAttributes = httpHeadersToSpanAttributes(headers); // 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 fda9b3f10b75..2416699cb2a6 100644 --- a/packages/remix/src/server/instrumentServer.ts +++ b/packages/remix/src/server/instrumentServer.ts @@ -310,10 +310,7 @@ function wrapRequestHandler ServerBuild | Promise [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', method: request.method, - ...httpHeadersToSpanAttributes( - winterCGHeadersToDict(request.headers), - clientOptions.sendDefaultPii ?? false, - ), + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)), }, }, async span => { diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index 26872a0f6f24..3d9963bd1056 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -3,7 +3,6 @@ import { continueTrace, debug, flushIfServerless, - getClient, getCurrentScope, getDefaultIsolationScope, getIsolationScope, @@ -179,10 +178,7 @@ async function instrumentHandle( [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeName ? 'route' : 'url', 'sveltekit.tracing.original_name': originalName, - ...httpHeadersToSpanAttributes( - winterCGHeadersToDict(event.request.headers), - getClient()?.getOptions().sendDefaultPii ?? false, - ), + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(event.request.headers)), }); } @@ -208,10 +204,7 @@ 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), - getClient()?.getOptions().sendDefaultPii ?? false, - ), + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(event.request.headers)), }, name: routeName, }, From cda821fbd6557236255419815cab9d85eea807f3 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:44:29 +0100 Subject: [PATCH 3/4] remove second parameter --- packages/core/test/lib/utils/request.test.ts | 2 +- packages/nextjs/src/common/utils/addHeadersAsAttributes.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index e0cf231205fd..328aebf29209 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -631,7 +631,7 @@ describe('request utils', () => { }); }); - it('filters comprehensive list of sensitive headers when $description', () => { + it('always filters comprehensive list of sensitive headers', () => { const headers = { 'Content-Type': 'application/json', 'User-Agent': 'test-agent', diff --git a/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts b/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts index 35abc4077ec6..ff025fc3ecc7 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 { getClient, httpHeadersToSpanAttributes, winterCGHeadersToDict } from '@sentry/core'; +import { httpHeadersToSpanAttributes, winterCGHeadersToDict } from '@sentry/core'; /** * Extracts HTTP request headers as span attributes and optionally applies them to a span. @@ -12,9 +12,6 @@ export function addHeadersAsAttributes( return {}; } - const client = getClient(); - const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; - const headersDict: Record = headers instanceof Headers || (typeof headers === 'object' && 'get' in headers) ? winterCGHeadersToDict(headers as Headers) From c4e7db6cae62c6e5ec4133cd4c516bcb1b3ba420 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:19:41 +0100 Subject: [PATCH 4/4] remove unused import --- packages/bun/src/integrations/bunserver.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index 0c88331b5175..73998e529349 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -3,7 +3,6 @@ import { captureException, continueTrace, defineIntegration, - getClient, httpHeadersToSpanAttributes, isURLObjectRelative, parseStringToURLObject, @@ -207,7 +206,6 @@ function wrapRequestHandler( routeName = route; } - const client = getClient(); Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON())); isolationScope.setSDKProcessingMetadata({