diff --git a/packages/cloudflare/src/integrations/httpServer.ts b/packages/cloudflare/src/integrations/httpServer.ts index 31747e11a4d1..f9643278098b 100644 --- a/packages/cloudflare/src/integrations/httpServer.ts +++ b/packages/cloudflare/src/integrations/httpServer.ts @@ -85,6 +85,8 @@ export async function captureIncomingRequestBody(client: Client, request: Reques return; } + // TODO(v11): Gate incoming request body capture on `dataCollection.httpBodies` (capture only when + // `'incomingRequest'` is listed) instead of defaulting to `'medium'`. const maxRequestBodySize = integration.maxRequestBodySize; if (maxRequestBodySize === 'none') { diff --git a/packages/cloudflare/src/request.ts b/packages/cloudflare/src/request.ts index a4c42ccbd7e8..40bd27a7b1e8 100644 --- a/packages/cloudflare/src/request.ts +++ b/packages/cloudflare/src/request.ts @@ -74,7 +74,7 @@ export function wrapRequestHandler( attributes, httpHeadersToSpanAttributes( winterCGHeadersToDict(request.headers), - getClient()?.getOptions().sendDefaultPii ?? false, + getClient()?.getDataCollectionOptions() ?? false, ), ); diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index b957fabe1e70..fb44121422e7 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -24,7 +24,10 @@ import { defaultStackParser } from './vendor/stacktrace'; /** Get the default integrations for the Cloudflare SDK. */ export function getDefaultIntegrations(options: CloudflareOptions): Integration[] { - const sendDefaultPii = options.sendDefaultPii ?? false; + // 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`. + 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. @@ -38,8 +41,7 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[ fetchIntegration(), honoIntegration(), httpServerIntegration(), - // TODO(v11): the `include` object should be defined directly in the integration based on `sendDefaultPii` - requestDataIntegration(sendDefaultPii ? undefined : { include: { cookies: false } }), + requestDataIntegration(cookiesEnabled ? undefined : { include: { cookies: false } }), consoleIntegration(), ]; } diff --git a/packages/cloudflare/test/request.test.ts b/packages/cloudflare/test/request.test.ts index 2164989833b3..4e81bd254258 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -205,6 +205,9 @@ describe('withSentry', () => { expect(sentryEvent.contexts?.culture).toEqual({ timezone: 'UTC' }); }); + // TODO(v11): Body capture should be gated on `dataCollection.httpBodies` (only capture when + // `'incomingRequest'` is listed). Until then we keep the historical behavior of capturing + // incoming request bodies by default at `'medium'`, consistent with the Node SDK. test('captures request body with default integration (medium size)', async () => { let sentryEvent: Event = {}; const context = createMockExecutionContext(); @@ -237,6 +240,82 @@ describe('withSentry', () => { ); }); + // 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 () => { + let sentryEvent: Event = {}; + + await wrapRequestHandler( + { + options: { + ...MOCK_OPTIONS, + beforeSend(event) { + sentryEvent = event; + return null; + }, + }, + request: new Request('https://example.com', { headers: { cookie: 'foo=bar' } }), + context: createMockExecutionContext(), + }, + () => { + SentryCore.captureMessage('cookies'); + return new Response('test'); + }, + ); + + expect(sentryEvent.request?.cookies).toBeUndefined(); + }); + + test('captures cookies when dataCollection.cookies is enabled', async () => { + let sentryEvent: Event = {}; + + await wrapRequestHandler( + { + options: { + ...MOCK_OPTIONS, + dataCollection: { cookies: true }, + beforeSend(event) { + sentryEvent = event; + return null; + }, + }, + request: new Request('https://example.com', { headers: { cookie: 'foo=bar' } }), + context: createMockExecutionContext(), + }, + () => { + SentryCore.captureMessage('cookies'); + return new Response('test'); + }, + ); + + expect(sentryEvent.request?.cookies).toEqual({ foo: 'bar' }); + }); + + test('captures cookies when sendDefaultPii is enabled', async () => { + let sentryEvent: Event = {}; + + await wrapRequestHandler( + { + options: { + ...MOCK_OPTIONS, + sendDefaultPii: true, + beforeSend(event) { + sentryEvent = event; + return null; + }, + }, + request: new Request('https://example.com', { headers: { cookie: 'foo=bar' } }), + context: createMockExecutionContext(), + }, + () => { + SentryCore.captureMessage('cookies'); + return new Response('test'); + }, + ); + + expect(sentryEvent.request?.cookies).toEqual({ foo: 'bar' }); + }); + test('does not capture request body for GET requests', async () => { let sentryEvent: Event = {}; const context = createMockExecutionContext();