From 1ea5a81f147e638e7757ca40dac5a54cc4537ece Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:12:19 +0200 Subject: [PATCH 1/3] feat(core)!: Remove `sendDefaultPii` in favor of `dataCollection` --- .agents/skills/add-ai-integration/SKILL.md | 2 +- .../instrumentation-client.ts | 1 - .../sentry.edge.config.ts | 1 - .../sentry.server.config.ts | 1 - .../requestData-streamed/instrument.mjs | 2 - packages/astro/src/server/middleware.ts | 9 ++-- packages/cloudflare/src/request.ts | 14 +++--- packages/cloudflare/src/sdk.ts | 7 +-- packages/cloudflare/test/request.test.ts | 6 +-- .../integrations/http/server-subscription.ts | 2 +- .../core/src/integrations/mcp-server/types.ts | 4 +- packages/core/src/tracing/langchain/types.ts | 4 +- packages/core/src/types/options.ts | 18 +------ .../defaultPiiToCollectionOptions.ts | 42 ---------------- .../resolveDataCollectionOptions.ts | 40 ++++++--------- packages/core/src/utils/request.ts | 12 ++--- .../resolveDataCollectionOptions.test.ts | 12 ----- packages/core/test/lib/utils/request.test.ts | 50 +++++++++---------- .../common/utils/addHeadersAsAttributes.ts | 6 +-- .../src/runtime/hooks/captureTracingEvents.ts | 11 ++-- .../runtime/hooks/wrapMiddlewareHandler.ts | 8 +-- packages/remix/src/server/instrumentServer.ts | 6 +-- packages/server-utils/src/vercel-ai/index.ts | 4 +- .../sveltekit/src/server-common/handle.ts | 15 +++--- 24 files changed, 88 insertions(+), 189 deletions(-) delete mode 100644 packages/core/src/utils/data-collection/defaultPiiToCollectionOptions.ts diff --git a/.agents/skills/add-ai-integration/SKILL.md b/.agents/skills/add-ai-integration/SKILL.md index 8323aa86fd56..25d508ccc50a 100644 --- a/.agents/skills/add-ai-integration/SKILL.md +++ b/.agents/skills/add-ai-integration/SKILL.md @@ -95,7 +95,7 @@ Reference: `packages/node/src/integrations/tracing/langchain/` ## Key Rules -1. Respect `sendDefaultPii` for `recordInputs`/`recordOutputs` +1. Respect `dataCollection.genAI` for recording input and output messages 2. Set `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = 'auto.ai.{provider}'` (alphanumerics, `_`, `.` only) 3. Truncate large data with helper functions from `utils.ts` 4. `gen_ai.invoke_agent` for parent ops, `gen_ai.chat` for child ops diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/instrumentation-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/instrumentation-client.ts index e2009fc21abb..5ac15cf2e88c 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/instrumentation-client.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/instrumentation-client.ts @@ -5,7 +5,6 @@ Sentry.init({ dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1.0, - sendDefaultPii: true, integrations: [Sentry.spanStreamingIntegration()], }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/sentry.edge.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/sentry.edge.config.ts index f2e946f81728..9ace249c414f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/sentry.edge.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/sentry.edge.config.ts @@ -5,7 +5,6 @@ Sentry.init({ dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1.0, - sendDefaultPii: true, traceLifecycle: 'stream', integrations: [Sentry.spanStreamingIntegration()], }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/sentry.server.config.ts index d370665d7dfd..6ff728c03155 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/sentry.server.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/sentry.server.config.ts @@ -5,7 +5,6 @@ Sentry.init({ dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1.0, - sendDefaultPii: true, traceLifecycle: 'stream', integrations: [Sentry.vercelAIIntegration(), Sentry.spanStreamingIntegration()], }); diff --git a/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/instrument.mjs index 994216708f44..53b9511a21f0 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/instrument.mjs @@ -7,6 +7,4 @@ Sentry.init({ tracesSampleRate: 1.0, transport: loggingTransport, traceLifecycle: 'stream', - // todo(v11): bridge-regression counterpart to instrument-with-datacollection.mjs; remove when sendDefaultPii is dropped in v11 - sendDefaultPii: true, }); diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 2b846fe68788..22263a81b42f 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -175,6 +175,10 @@ async function instrumentRequestStartHttpServerSpan( } const request = ctx.request; + const client = getClient(); + if (!client) { + return next(); + } // Note: We guard outside of this function call that the request is dynamic // accessing headers on a static route would throw @@ -212,10 +216,7 @@ async function instrumentRequestStartHttpServerSpan( method, [URL_FULL]: ctx.url.href, [URL_PATH]: ctx.url.pathname, - ...httpHeadersToSpanAttributes( - winterCGHeadersToDict(request.headers), - getClient()?.getDataCollectionOptions() ?? false, - ), + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), client.getDataCollectionOptions()), }; if (parametrizedRoute) { diff --git a/packages/cloudflare/src/request.ts b/packages/cloudflare/src/request.ts index 2adf4cbf2c24..1edb1da26296 100644 --- a/packages/cloudflare/src/request.ts +++ b/packages/cloudflare/src/request.ts @@ -2,7 +2,6 @@ import type { CfProperties, IncomingRequestCfProperties } from '@cloudflare/work import { captureException, continueTrace, - getClient, getHttpSpanDetailsFromUrlObject, httpHeadersToSpanAttributes, parseStringToURLObject, @@ -77,13 +76,12 @@ export function wrapRequestHandler( attributes['user_agent.original'] = userAgentHeader; } - Object.assign( - attributes, - httpHeadersToSpanAttributes( - winterCGHeadersToDict(request.headers), - getClient()?.getDataCollectionOptions() ?? false, - ), - ); + if (client) { + Object.assign( + attributes, + httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), client.getDataCollectionOptions()), + ); + } attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index ac85151846f0..23c4d393b0fc 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -41,11 +41,6 @@ function getRegisteredChannelIntegrations(): Integration[] { /** Get the default integrations for the Cloudflare SDK. */ export function getDefaultIntegrations(options: CloudflareOptions): Integration[] { - // TODO(v11): Drop this transitional gating and let `requestDataIntegration` rely on the resolved - // `dataCollection` defaults directly. Until then, preserve the historical Cloudflare behavior of not - // attaching cookies unless the user explicitly opts in via `sendDefaultPii` or `dataCollection.cookies`. - // eslint-disable-next-line typescript/no-deprecated - const cookiesEnabled = options.sendDefaultPii || options.dataCollection?.cookies != null; return [ // The Dedupe integration should not be used in workflows because we want to // capture all step failures, even if they are the same error. @@ -58,7 +53,7 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[ linkedErrorsIntegration(), fetchIntegration(), httpServerIntegration(), - requestDataIntegration(cookiesEnabled ? undefined : { include: { cookies: false } }), + requestDataIntegration(), consoleIntegration(), // The orchestrion diagnostics-channel subscribers (mysql, pg, …). The // `@sentry/cloudflare/vite` plugin injects the channels at build time and, diff --git a/packages/cloudflare/test/request.test.ts b/packages/cloudflare/test/request.test.ts index e991dc468646..c4d1ee30b074 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -299,9 +299,7 @@ describe('withSentry', () => { expect(sentryEvent.sdkProcessingMetadata?.normalizedRequest?.data).toEqual(JSON.stringify({ key: 'value' })); }); - // TODO(v11): Cookies should be attached (subject to denylist filtering) by default. Until then we keep the - // historical Cloudflare behavior of not attaching cookies unless the user explicitly opts in. - test('does not capture cookies by default', async () => { + test('captures cookies by default', async () => { let sentryEvent: Event = {}; await wrapRequestHandler( @@ -322,7 +320,7 @@ describe('withSentry', () => { }, ); - expect(sentryEvent.request?.cookies).toBeUndefined(); + expect(sentryEvent.request?.cookies).toEqual({ foo: 'bar' }); }); test('does not capture request body for GET requests', async () => { diff --git a/packages/core/src/integrations/http/server-subscription.ts b/packages/core/src/integrations/http/server-subscription.ts index 98ade1cac0f6..0cc3a5de70a7 100644 --- a/packages/core/src/integrations/http/server-subscription.ts +++ b/packages/core/src/integrations/http/server-subscription.ts @@ -330,7 +330,7 @@ function buildServerSpanWrap( 'http.status_code': response.statusCode, ...httpHeadersToSpanAttributes( headersToDict(response.headers), - client?.getDataCollectionOptions() ?? false, + client.getDataCollectionOptions(), 'response', ), }); diff --git a/packages/core/src/integrations/mcp-server/types.ts b/packages/core/src/integrations/mcp-server/types.ts index 2749e59c1b32..613d5dd5ad77 100644 --- a/packages/core/src/integrations/mcp-server/types.ts +++ b/packages/core/src/integrations/mcp-server/types.ts @@ -225,9 +225,9 @@ export type SessionData = { * Options for configuring the MCP server wrapper. */ export type McpServerWrapperOptions = { - /** Whether to capture tool/prompt input arguments in spans. Defaults to `dataCollection.genAI.inputs` (or `sendDefaultPii` when `dataCollection` is not configured). */ + /** Whether to capture tool/prompt input arguments in spans. Defaults to `dataCollection.genAI.inputs`. */ recordInputs?: boolean; - /** Whether to capture tool/prompt output results in spans. Defaults to `dataCollection.genAI.outputs` (or `sendDefaultPii` when `dataCollection` is not configured). */ + /** Whether to capture tool/prompt output results in spans. Defaults to `dataCollection.genAI.outputs`. */ recordOutputs?: boolean; }; diff --git a/packages/core/src/tracing/langchain/types.ts b/packages/core/src/tracing/langchain/types.ts index aae03680c0d1..7acc6409695b 100644 --- a/packages/core/src/tracing/langchain/types.ts +++ b/packages/core/src/tracing/langchain/types.ts @@ -4,13 +4,13 @@ export interface LangChainOptions { /** * Whether to record input messages/prompts - * @default false (respects `dataCollection.genAI.inputs`, or `sendDefaultPii` when `dataCollection` is not configured) + * @default false (respects `dataCollection.genAI.inputs`) */ recordInputs?: boolean; /** * Whether to record output text and responses - * @default false (respects `dataCollection.genAI.outputs`, or `sendDefaultPii` when `dataCollection` is not configured) + * @default false (respects `dataCollection.genAI.outputs`) */ recordOutputs?: boolean; diff --git a/packages/core/src/types/options.ts b/packages/core/src/types/options.ts index e5f938082509..ff7ff69dd051 100644 --- a/packages/core/src/types/options.ts +++ b/packages/core/src/types/options.ts @@ -370,27 +370,11 @@ export interface ClientOptions, - // TODO(v11): Remove boolean support once sendDefaultPii is fully removed. - // Internally, always pass ResolvedDataCollection from client.getDataCollectionOptions(). - dataCollection: ResolvedDataCollection | boolean = false, + dataCollection: ResolvedDataCollection, lifecycle: 'request' | 'response' = 'request', ): Record { - const resolvedDataCollection = - typeof dataCollection === 'boolean' ? defaultPiiToCollectionOptions(dataCollection) : dataCollection; - const headerBehavior = - lifecycle === 'request' ? resolvedDataCollection.httpHeaders.request : resolvedDataCollection.httpHeaders.response; - const cookieBehavior = resolvedDataCollection.cookies; + lifecycle === 'request' ? dataCollection.httpHeaders.request : dataCollection.httpHeaders.response; + const cookieBehavior = dataCollection.cookies; const prefix = `http.${lifecycle}.header.`; const spanAttributes: Record = {}; diff --git a/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts b/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts index 04d785b883bc..87700ed83072 100644 --- a/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts +++ b/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it } from 'vitest'; -import { defaultPiiToCollectionOptions } from '../../../../src/utils/data-collection/defaultPiiToCollectionOptions'; import { resolveDataCollectionOptions } from '../../../../src/utils/data-collection/resolveDataCollectionOptions'; describe('resolveDataCollectionOptions', () => { @@ -24,17 +23,6 @@ describe('resolveDataCollectionOptions', () => { it('returns spec defaults when dataCollection is explicitly set to empty object', () => { expect(resolveDataCollectionOptions({ dataCollection: {} })).toEqual(SPEC_DEFAULTS); }); - - it('ignores sendDefaultPii when dataCollection is set', () => { - expect(resolveDataCollectionOptions({ dataCollection: {}, sendDefaultPii: false })).toEqual(SPEC_DEFAULTS); - }); - - it.each([true, false])( - 'uses the legacy bridge when sendDefaultPii is %s and dataCollection is absent', - sendDefaultPii => { - expect(resolveDataCollectionOptions({ sendDefaultPii })).toEqual(defaultPiiToCollectionOptions(sendDefaultPii)); - }, - ); }); describe('dataCollection options', () => { diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index 1569fc6a581a..4db75d5a96ff 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -441,7 +441,7 @@ describe('request utils', () => { describe('httpHeadersToSpanAttributes', () => { it('works with empty headers object', () => { - expect(httpHeadersToSpanAttributes({})).toEqual({}); + expect(httpHeadersToSpanAttributes({}, resolveDataCollectionOptions({}))).toEqual({}); }); it('converts single string header values to strings', () => { @@ -450,7 +450,7 @@ describe('request utils', () => { 'user-agent': 'test-agent', }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.content_type': 'application/json', @@ -464,7 +464,7 @@ describe('request utils', () => { accept: ['application/json', 'text/html'], }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.custom_header': 'value1;value2', @@ -478,7 +478,7 @@ describe('request utils', () => { 'valid-header': 'valid-value', } as any; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.valid_header': 'valid-value', @@ -492,7 +492,7 @@ describe('request utils', () => { 'undefined-header': undefined, }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.valid_header': 'valid-value', @@ -505,7 +505,7 @@ describe('request utils', () => { 'valid-header': 'valid-value', } as any; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.empty_header': '', @@ -521,7 +521,7 @@ describe('request utils', () => { ACCEPT: 'text/html', }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.content_type': 'application/json', @@ -544,7 +544,7 @@ describe('request utils', () => { 'X-Forwarded-For': '192.168.1.1', }; - const result = httpHeadersToSpanAttributes(headers, true); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.host': 'example.com', @@ -565,7 +565,7 @@ describe('request utils', () => { Accept: ['application/json', 'text/html'], }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.x_random_header': 'test=abc123;preferences=dark-mode;number=three', @@ -579,7 +579,7 @@ describe('request utils', () => { 'valid-header': 'valid-value', }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.empty_header': '', @@ -596,7 +596,7 @@ describe('request utils', () => { }, }); - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({}); }); @@ -606,7 +606,7 @@ describe('request utils', () => { 'mixed-types': ['string-value', 123, true, null], } as any; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.mixed_types': 'string-value;123;true;', @@ -622,7 +622,7 @@ describe('request utils', () => { 'object-header': { key: 'value' }, } as any; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.string_header': 'valid-value', @@ -638,7 +638,7 @@ describe('request utils', () => { 'Content-Type': 'application/json', }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.content_type': 'application/json', @@ -654,7 +654,7 @@ describe('request utils', () => { 'session=abc123; tracking=enabled; cookie-authentication-key-without-value; theme=dark; lang=en; user_session=xyz789; pref=1', }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.cookie.session': '[Filtered]', @@ -673,7 +673,7 @@ describe('request utils', () => { 'connect.sid=s3cr3t; express.sid=opaque; PHPSESSID=abcd; theme=light; sb-access-token=x; __stripe_mid=y', }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.cookie.connect.sid': '[Filtered]', @@ -709,11 +709,11 @@ describe('request utils', () => { 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); + const result1 = httpHeadersToSpanAttributes(headers1, resolveDataCollectionOptions({})); expect(result1).toEqual({ 'http.request.header.cookie': '[Filtered]' }); const headers3 = { Cookie: '' }; - const result3 = httpHeadersToSpanAttributes(headers3); + const result3 = httpHeadersToSpanAttributes(headers3, resolveDataCollectionOptions({})); expect(result3).toEqual({ 'http.request.header.cookie': '[Filtered]' }); }); @@ -729,13 +729,13 @@ describe('request utils', () => { ['empty=; Secure', { 'http.request.header.set_cookie.empty': '' }], ])('should parse and filter Set-Cookie header: %s', (setCookieValue, expected) => { const headers = { 'Set-Cookie': setCookieValue }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual(expected); }); 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); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ 'http.request.header.cookie.random_string': 'eyJhbGc=.eyJzdWI=.SflKxw' }); }); @@ -810,9 +810,9 @@ describe('request utils', () => { 'x-saml-token': 'saml', }; - const result = httpHeadersToSpanAttributes(headers); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); - // Sensitive headers are always included and redacted + // Security-sensitive headers remain redacted with permissive collection defaults. expect(result).toEqual({ 'http.request.header.content_type': 'application/json', 'http.request.header.user_agent': 'test-agent', @@ -833,7 +833,7 @@ describe('request utils', () => { '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_user': 'user', 'http.request.header.x_forwarded_authorization': '[Filtered]', 'http.request.header.x_jwt_token': '[Filtered]', 'http.request.header.x_bearer_token': '[Filtered]', @@ -861,7 +861,7 @@ describe('request utils', () => { Cookie: 'session=abc123', }; - const result = httpHeadersToSpanAttributes(headers, false, 'response'); + const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({}), 'response'); expect(result).toEqual({ 'http.response.header.host': 'example.com', @@ -872,7 +872,7 @@ describe('request utils', () => { 'http.response.header.connection': 'keep-alive', 'http.response.header.upgrade_insecure_requests': '1', 'http.response.header.cache_control': 'no-cache', - 'http.response.header.x_forwarded_for': '[Filtered]', + 'http.response.header.x_forwarded_for': '192.168.1.1', 'http.response.header.authorization': '[Filtered]', 'http.response.header.x_bearer_token': '[Filtered]', 'http.response.header.x_saml_token': '[Filtered]', diff --git a/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts b/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts index f91cb127c382..d61e6ab94e8e 100644 --- a/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts +++ b/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts @@ -13,9 +13,7 @@ export function addHeadersAsAttributes( } const client = getClient(); - const dataCollection = client?.getDataCollectionOptions(); - - if (dataCollection?.httpHeaders.request === false) { + if (!client || client.getDataCollectionOptions().httpHeaders.request === false) { return {}; } @@ -24,7 +22,7 @@ export function addHeadersAsAttributes( ? winterCGHeadersToDict(headers as Headers) : headers; - const headerAttributes = httpHeadersToSpanAttributes(headersDict, dataCollection ?? false); + const headerAttributes = httpHeadersToSpanAttributes(headersDict, client.getDataCollectionOptions()); if (span) { span.setAttributes(headerAttributes); diff --git a/packages/nitro/src/runtime/hooks/captureTracingEvents.ts b/packages/nitro/src/runtime/hooks/captureTracingEvents.ts index faad0238a5d7..eed65673200f 100644 --- a/packages/nitro/src/runtime/hooks/captureTracingEvents.ts +++ b/packages/nitro/src/runtime/hooks/captureTracingEvents.ts @@ -162,10 +162,13 @@ function setupSrvxTracingChannels(): void { method: data.request.method, }); - const headerAttributes = httpHeadersToSpanAttributes( - Object.fromEntries(data.request.headers.entries()), - getClient()?.getDataCollectionOptions() ?? false, - ); + const client = getClient(); + const headerAttributes = client + ? httpHeadersToSpanAttributes( + Object.fromEntries(data.request.headers.entries()), + client.getDataCollectionOptions(), + ) + : {}; return startInactiveSpan({ name: spanName, diff --git a/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts b/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts index 7c024388b948..988e3b36c70e 100644 --- a/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts +++ b/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts @@ -202,10 +202,10 @@ function getSpanAttributes( headers = Object.fromEntries(eventH3v2?.req.headers.entries()); } - const headerAttributes = httpHeadersToSpanAttributes(headers, getClient()?.getDataCollectionOptions() ?? false); - - // Merge header attributes with existing attributes - Object.assign(attributes, headerAttributes); + const client = getClient(); + if (client) { + Object.assign(attributes, httpHeadersToSpanAttributes(headers, client.getDataCollectionOptions())); + } return attributes; } diff --git a/packages/remix/src/server/instrumentServer.ts b/packages/remix/src/server/instrumentServer.ts index 820e524f578e..53a7fd98307a 100644 --- a/packages/remix/src/server/instrumentServer.ts +++ b/packages/remix/src/server/instrumentServer.ts @@ -332,7 +332,7 @@ function wrapRequestHandler ServerBuild | Promise } return withIsolationScope(async isolationScope => { - const clientOptions = getClient()?.getOptions(); + const client = getClient(); let normalizedRequest: RequestEventData = {}; @@ -364,7 +364,7 @@ function wrapRequestHandler ServerBuild | Promise isolationScope.setSDKProcessingMetadata({ normalizedRequest }); - if (!clientOptions || !hasSpansEnabled(clientOptions)) { + if (!client || !hasSpansEnabled(client.getOptions())) { return origRequestHandler.call(this, request, loadContext); } @@ -394,7 +394,7 @@ function wrapRequestHandler ServerBuild | Promise }), ...httpHeadersToSpanAttributes( winterCGHeadersToDict(request.headers), - getClient()?.getDataCollectionOptions(), + client.getDataCollectionOptions(), ), }, }, diff --git a/packages/server-utils/src/vercel-ai/index.ts b/packages/server-utils/src/vercel-ai/index.ts index 64296801c322..8fb746149da0 100644 --- a/packages/server-utils/src/vercel-ai/index.ts +++ b/packages/server-utils/src/vercel-ai/index.ts @@ -4,14 +4,14 @@ import * as dc from 'node:diagnostics_channel'; export interface VercelAiOptions { /** - * Enable or disable input recording. Enabled if `dataCollection.genAI.inputs` (or the deprecated `sendDefaultPii` option) is `true` + * Enable or disable input recording. Enabled if `dataCollection.genAI.inputs` is `true` * or if you set `isEnabled` to `true` in your ai SDK method telemetry settings. * Integration-level options take precedence over global `dataCollection` config. */ recordInputs?: boolean; /** - * Enable or disable output recording. Enabled if `dataCollection.genAI.outputs` (or the deprecated `sendDefaultPii` option) is `true` + * Enable or disable output recording. Enabled if `dataCollection.genAI.outputs` is `true` * or if you set `isEnabled` to `true` in your ai SDK method telemetry settings. * Integration-level options take precedence over global `dataCollection` config. */ diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index 5a852c9eb779..1e306dfa9281 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -148,6 +148,7 @@ async function instrumentHandle( // - Used Kit version doesn't yet support tracing // - Users didn't enable tracing const kitTracingEnabled = event.tracing?.enabled; + const dataCollectionOptions = getClient()?.getDataCollectionOptions(); try { const resolveWithSentry: (sentrySpan?: Span) => Promise = async (sentrySpan?: Span) => { @@ -185,10 +186,9 @@ async function instrumentHandle( ...(routeName && { [HTTP_ROUTE]: routeName, }), - ...httpHeadersToSpanAttributes( - winterCGHeadersToDict(event.request.headers), - getClient()?.getDataCollectionOptions() ?? false, - ), + ...(dataCollectionOptions + ? httpHeadersToSpanAttributes(winterCGHeadersToDict(event.request.headers), dataCollectionOptions) + : {}), }); } @@ -219,10 +219,9 @@ async function instrumentHandle( ...(routeId && { [HTTP_ROUTE]: routeId, }), - ...httpHeadersToSpanAttributes( - winterCGHeadersToDict(event.request.headers), - getClient()?.getDataCollectionOptions() ?? false, - ), + ...(dataCollectionOptions + ? httpHeadersToSpanAttributes(winterCGHeadersToDict(event.request.headers), dataCollectionOptions) + : {}), }, name: routeName, }, From 6d375b3f7ccddaa9f31866442746aad8c8b8448a Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:21:36 +0200 Subject: [PATCH 2/3] add migration guide --- MIGRATION.md | 68 ++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index af7724fa7dbd..7855ecc17f54 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -148,57 +148,55 @@ Sentry.init({ Affected SDKs: All SDKs. -The `sendDefaultPii` option was **removed** and replaced by a more granular `dataCollection` option that controls each category of collected data individually. +> **Heads up — this is a behavior change, not just a renamed option.** +> In v10, leaving `sendDefaultPii` unset behaved like `sendDefaultPii: false` (restrictive). +> In v11, leaving `dataCollection` unset collects the categories below **by default**. +> Review this before upgrading if you'd rather not collect HTTP request data, database queries, or GenAI inputs/outputs. -The **default behaviour is now more permissive**. In v10, with neither `sendDefaultPii` nor `dataCollection` set, the SDK behaved like `sendDefaultPii: false`. In v11, the `dataCollection` defaults apply out of the box: +We've replaced `sendDefaultPii` with `dataCollection`, which controls each category of collected data individually. The default is now more permissive than in v10. -| Category | v10 default (no `sendDefaultPii`) | v11 default | -| --------------------- | --------------------------------- | -------------------- | -| `userInfo` | `false` | `true` | -| `cookies` | sensitive keys denied | `true` | -| `httpHeaders` | sensitive keys denied | request + response | -| `httpBodies` | none (`[]`) | all request/response | -| `urlQueryParams` | sensitive keys denied | `true` | -| `genAI` | inputs/outputs off | inputs + outputs on | -| `stackFrameVariables` | `true` | `true` | -| `frameContextLines` | `5` | `5` | +| Category | v10 default (`sendDefaultPii` off) | v11 default | +| --------------------- | ---------------------------------- | -------------------- | +| `userInfo` | `false` | `true` | +| `cookies` | not collected | `true` | +| `httpHeaders` | request + response, PII scrubbed | request + response | +| `httpBodies` | not collected (size only) | all request/response | +| `urlQueryParams` | `true` | `true` | +| `genAI` | inputs + outputs not collected | inputs + outputs | +| `databaseQueryData` | `false` | `true` | +| `stackFrameVariables` | `true` | `true` | +| `frameContextLines` | `7` | `5` | -> Sensitive values (keys, tokens, auth headers, etc.) are always filtered out regardless of these settings. +> Sentry's built-in sensitive-data filtering still applies. Review your data-scrubbing config for categories that may contain sensitive values — especially request/response bodies. -Migration: +#### If you previously set `sendDefaultPii: true` + +The v11 default matches this, so just remove the option: ```js -// before (v10) — collect the default set of PII -Sentry.init({ - sendDefaultPii: true, -}); +// v10 +Sentry.init({ sendDefaultPii: true }); -// after (v11) — this is now the default; you can remove the option entirely, -// or opt into specific categories explicitly: -Sentry.init({ - dataCollection: { - userInfo: true, - cookies: true, - httpHeaders: { request: true, response: true }, - urlQueryParams: true, - genAI: { inputs: true, outputs: true }, - }, -}); +// v11 — same behavior is now the default +Sentry.init({}); ``` -If you previously relied on the restrictive default (`sendDefaultPii: false` or unset) and want to -keep collecting as little data as possible, you now need to opt out explicitly: +#### If you want to keep the v10 default behavior + +Set the baseline explicitly. **Don't leave `dataCollection` unset** — that now enables broader collection. ```js -// after (v11) — restrict data collection to the v10-like minimum +// v11 — preserves the v10 default Sentry.init({ dataCollection: { userInfo: false, cookies: false, - httpHeaders: { request: false, response: false }, + httpHeaders: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] }, httpBodies: [], - urlQueryParams: false, + urlQueryParams: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] }, genAI: { inputs: false, outputs: false }, + databaseQueryData: false, + graphQL: { document: false, variables: false }, }, }); ``` @@ -206,6 +204,8 @@ Sentry.init({ Each key-value field (`cookies`, `urlQueryParams`, `httpHeaders.request`, `httpHeaders.response`) accepts `true`, `false`, `{ allow: string[] }`, or `{ deny: string[] }` for fine-grained control. +See the [`dataCollection` docs](https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection) for the full option list. + #### RequestData The `requestDataIntegration`'s `include` options remain an integration-level override. An explicit `false` From f54eaedb0c8b5324c16ab6674bac60141592b0f8 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:45:04 +0200 Subject: [PATCH 3/3] fix build --- packages/bun/src/integrations/bunserver.ts | 8 ++++++-- .../core/src/integrations/http/server-subscription.ts | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index d6fd4650e7cb..6b01de8040aa 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -221,7 +221,9 @@ function wrapRequestHandler( const client = getClient(); const dataCollection = client?.getDataCollectionOptions(); - Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), dataCollection)); + if (dataCollection) { + Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), dataCollection)); + } isolationScope.setSDKProcessingMetadata({ normalizedRequest: { @@ -254,7 +256,9 @@ function wrapRequestHandler( status_code: response.status, }); - span.setAttributes(httpHeadersToSpanAttributes(response.headers.toJSON(), dataCollection, 'response')); + if (dataCollection) { + span.setAttributes(httpHeadersToSpanAttributes(response.headers.toJSON(), dataCollection, 'response')); + } } return response; } catch (e) { diff --git a/packages/core/src/integrations/http/server-subscription.ts b/packages/core/src/integrations/http/server-subscription.ts index 0cc3a5de70a7..d14b32da2cba 100644 --- a/packages/core/src/integrations/http/server-subscription.ts +++ b/packages/core/src/integrations/http/server-subscription.ts @@ -255,6 +255,8 @@ function buildServerSpanWrap( return next(); } + const dataCollectionOptions = client.getDataCollectionOptions(); + if ( shouldIgnoreSpansForIncomingRequest(request, { ignoreStaticAssets, @@ -308,7 +310,7 @@ function buildServerSpanWrap( 'http.flavor': httpVersion, 'net.transport': httpVersion?.toUpperCase() === 'QUIC' ? 'ip_udp' : 'ip_tcp', ...getRequestContentLengthAttribute(request), - ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, client.getDataCollectionOptions()), + ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, dataCollectionOptions), }, }, span => { @@ -330,7 +332,7 @@ function buildServerSpanWrap( 'http.status_code': response.statusCode, ...httpHeadersToSpanAttributes( headersToDict(response.headers), - client.getDataCollectionOptions(), + dataCollectionOptions, 'response', ), });