diff --git a/dev-packages/cloudflare-integration-tests/suites/basic/test.ts b/dev-packages/cloudflare-integration-tests/suites/basic/test.ts index 1f6480f4cab4..efb0f4c35331 100644 --- a/dev-packages/cloudflare-integration-tests/suites/basic/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/basic/test.ts @@ -21,6 +21,7 @@ it('Basic error in fetch handler', async ({ signal }) => { ], }, request: { + cookies: {}, headers: expect.any(Object), method: 'GET', url: expect.any(String), diff --git a/dev-packages/cloudflare-integration-tests/suites/double-instrumentation/test.ts b/dev-packages/cloudflare-integration-tests/suites/double-instrumentation/test.ts index ab0b2155349b..89939f53c64c 100644 --- a/dev-packages/cloudflare-integration-tests/suites/double-instrumentation/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/double-instrumentation/test.ts @@ -21,6 +21,7 @@ it('Only sends one error event when withSentry is called twice', async ({ signal ], }, request: { + cookies: {}, headers: expect.any(Object), method: 'GET', url: expect.any(String), diff --git a/dev-packages/cloudflare-integration-tests/suites/hono-sdk/test.ts b/dev-packages/cloudflare-integration-tests/suites/hono-sdk/test.ts index bbaa75aae4e8..b2ee4c937032 100644 --- a/dev-packages/cloudflare-integration-tests/suites/hono-sdk/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/hono-sdk/test.ts @@ -22,6 +22,7 @@ it('Hono app captures parametrized errors (Hono SDK)', async ({ signal }) => { ], }, request: { + cookies: {}, headers: expect.any(Object), method: 'GET', url: expect.stringContaining('/error/param-123'), diff --git a/dev-packages/cloudflare-integration-tests/suites/integrations/http-server/test.ts b/dev-packages/cloudflare-integration-tests/suites/integrations/http-server/test.ts index f36849462a42..68649db65b63 100644 --- a/dev-packages/cloudflare-integration-tests/suites/integrations/http-server/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/integrations/http-server/test.ts @@ -10,6 +10,7 @@ it('Captures JSON request body', async ({ signal }) => { level: 'info', message: 'POST JSON request', request: { + cookies: {}, headers: expect.any(Object), method: 'POST', url: expect.stringContaining('/post-json'), @@ -38,6 +39,7 @@ it('Captures form-urlencoded request body', async ({ signal }) => { level: 'info', message: 'POST form request', request: { + cookies: {}, headers: expect.any(Object), method: 'POST', url: expect.stringContaining('/post-form'), @@ -66,6 +68,7 @@ it('Captures plain text request body', async ({ signal }) => { level: 'info', message: 'POST text request', request: { + cookies: {}, headers: expect.any(Object), method: 'POST', url: expect.stringContaining('/post-text'), @@ -94,6 +97,7 @@ it('Does not capture body for POST without content', async ({ signal }) => { level: 'info', message: 'POST no body request', request: { + cookies: {}, headers: expect.any(Object), method: 'POST', url: expect.stringContaining('/post-no-body'), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/dsc-url-source/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/dsc-url-source/test.ts index 14ea900e179a..9118790e0758 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/dsc-url-source/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/dsc-url-source/test.ts @@ -23,6 +23,7 @@ it('omits the span name from the DSC for url-source spans when tracing is enable ], }, request: { + cookies: {}, headers: expect.any(Object), method: 'GET', url: expect.any(String), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/headers/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/headers/test.ts index a20ec16a354f..e73e04583ffb 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/headers/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/headers/test.ts @@ -46,6 +46,7 @@ it('Tracing headers', async ({ signal }) => { }, ], request: { + cookies: {}, headers: expect.any(Object), method: 'GET', url: expect.any(String), diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/callable.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/callable.test.ts index 0146f4e8724f..1eebb743f086 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/callable.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/callable.test.ts @@ -48,6 +48,7 @@ test('@callable() methods work correctly with Sentry instrumentAgentWithSentry', transaction: 'GET /agents/my-agent/user-123', type: 'transaction', request: { + cookies: {}, headers: expect.any(Object), method: 'GET', url: expect.stringContaining('/agents/my-agent/user-123'), diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index 3cb51d76e140..87ee09e12176 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. @@ -59,7 +54,7 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[ fetchIntegration(), httpServerIntegration(), // oxlint-disable-next-line typescript/no-deprecated - 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..242d40cdda6b 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -268,6 +268,31 @@ describe('withSentry', () => { expect(sentryEvent.sdkProcessingMetadata?.normalizedRequest?.data).toBeUndefined(); }); + test('captures cookies with denylist filtering by default', async () => { + let sentryEvent: Event = {}; + + await wrapRequestHandler( + { + options: { + ...MOCK_OPTIONS, + dataCollection: { httpBodies: [] }, + beforeSend(event) { + sentryEvent = event; + return null; + }, + }, + request: new Request('https://example.com', { headers: { cookie: 'foo=bar; session=secret' } }), + context: createMockExecutionContext(), + }, + () => { + SentryCore.captureMessage('request body'); + return new Response('test'); + }, + ); + + expect(sentryEvent.request?.cookies).toEqual({ foo: 'bar', session: '[Filtered]' }); + }); + test('explicit maxRequestBodySize overrides dataCollection.httpBodies', async () => { let sentryEvent: Event = {}; const context = createMockExecutionContext(); @@ -299,15 +324,14 @@ 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('does not capture cookies when dataCollection.cookies is disabled', async () => { let sentryEvent: Event = {}; await wrapRequestHandler( { options: { ...MOCK_OPTIONS, + dataCollection: { cookies: false }, beforeSend(event) { sentryEvent = event; return null; diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index edc095dd0f2b..bdb9b111f29f 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -63,7 +63,7 @@ export function init(options: VercelEdgeOptions = {}): void { ); } - const customDefaultIntegrations = getDefaultIntegrations(options); + const customDefaultIntegrations = getDefaultIntegrations(); // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index 903f3492648a..6346c55ea35a 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -1,5 +1,5 @@ import { diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api'; -import type { Client, Integration, Options } from '@sentry/core'; +import type { Client, Integration } from '@sentry/core'; import { consoleIntegration, conversationIdIntegration, @@ -35,9 +35,8 @@ declare const process: { const nodeStackParser = createStackParser(nodeStackLineParser()); -/** Get the default integrations for the browser SDK. */ -export function getDefaultIntegrations(_options: Options): Integration[] { - // todo(v11): remove options parameter +/** Get the default integrations for the Vercel Edge SDK. */ +export function getDefaultIntegrations(): Integration[] { return [ dedupeIntegration(), // TODO(v11): Replace with `eventFiltersIntegration` once we remove the deprecated `inboundFiltersIntegration` @@ -60,7 +59,7 @@ export function init(options: VercelEdgeOptions = {}): Client { scope.update(options.initialScope); if (options.defaultIntegrations === undefined) { - options.defaultIntegrations = getDefaultIntegrations(options); + options.defaultIntegrations = getDefaultIntegrations(); } if (options.dsn === undefined && process.env.SENTRY_DSN) { diff --git a/packages/vercel-edge/test/sdk.test.ts b/packages/vercel-edge/test/sdk.test.ts index c23848424290..b5e4522d8b73 100644 --- a/packages/vercel-edge/test/sdk.test.ts +++ b/packages/vercel-edge/test/sdk.test.ts @@ -1,6 +1,44 @@ import { describe, expect, it } from 'vitest'; -import { init, spanStreamingIntegration } from '../src'; -import { type Integration } from '@sentry/core'; +import { getDefaultIntegrations, init, spanStreamingIntegration } from '../src'; +import { type Event, type Integration } from '@sentry/core'; + +describe('getDefaultIntegrations', () => { + it('includes request data collection by default', () => { + expect(getDefaultIntegrations().map(integration => integration.name)).toContain('RequestData'); + }); + + it('collects and filters request cookies by default', () => { + const client = init({ skipOpenTelemetrySetup: true }); + const requestDataIntegration = getDefaultIntegrations().find(integration => integration.name === 'RequestData'); + const event: Event = { + sdkProcessingMetadata: { + normalizedRequest: { headers: { cookie: 'theme=dark; session=secret' } }, + }, + }; + + requestDataIntegration?.processEvent?.(event, {}, client); + + expect(requestDataIntegration).toBeDefined(); + expect(event.request?.cookies).toEqual({ theme: 'dark', session: '[Filtered]' }); + expect(event.request?.headers?.cookie).toBe('[Filtered]'); + }); + + it('does not collect request cookies when dataCollection.cookies is disabled', () => { + const client = init({ dataCollection: { cookies: false }, skipOpenTelemetrySetup: true }); + const requestDataIntegration = getDefaultIntegrations().find(integration => integration.name === 'RequestData'); + const event: Event = { + sdkProcessingMetadata: { + normalizedRequest: { headers: { cookie: 'theme=dark' } }, + }, + }; + + requestDataIntegration?.processEvent?.(event, {}, client); + + expect(requestDataIntegration).toBeDefined(); + expect(event.request?.cookies).toBeUndefined(); + expect(event.request?.headers).toEqual({}); + }); +}); describe('init', () => { it('adds spanStreamingIntegration by default', () => {