From 97b150073f10ed97b4476f815536eba74739c003 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 29 May 2026 10:31:57 +0200 Subject: [PATCH 1/2] migrate --- .../cloudflare/src/integrations/httpServer.ts | 14 +++++--- packages/cloudflare/src/request.ts | 2 +- packages/cloudflare/src/sdk.ts | 4 +-- packages/cloudflare/test/request.test.ts | 33 +++++++++++++++++-- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/packages/cloudflare/src/integrations/httpServer.ts b/packages/cloudflare/src/integrations/httpServer.ts index 31747e11a4d1..0beb462087cd 100644 --- a/packages/cloudflare/src/integrations/httpServer.ts +++ b/packages/cloudflare/src/integrations/httpServer.ts @@ -14,10 +14,11 @@ export interface HttpServerIntegrationOptions { * Available options: * - `'none'`: No request bodies will be attached * - `'small'`: Request bodies up to 1,000 bytes will be attached - * - `'medium'`: Request bodies up to 10,000 bytes will be attached (default) + * - `'medium'`: Request bodies up to 10,000 bytes will be attached * - `'always'`: Request bodies will always be attached (up to 1MB limit) * - * @default 'medium' + * When not set, falls back to `dataCollection.httpBodies`: if `'incomingRequest'` + * is listed, bodies are captured at `'medium'` size; otherwise no bodies are captured. */ maxRequestBodySize?: MaxRequestBodySize; @@ -42,14 +43,14 @@ export interface HttpServerIntegrationOptions { interface HttpServerIntegrationInstance { name: string; - maxRequestBodySize: MaxRequestBodySize; + maxRequestBodySize: MaxRequestBodySize | undefined; ignoreRequestBody?: (url: string, request: Request) => boolean; } const _httpServerIntegration = ((options: HttpServerIntegrationOptions = {}): HttpServerIntegrationInstance => { return { name: INTEGRATION_NAME, - maxRequestBodySize: options.maxRequestBodySize ?? 'medium', + maxRequestBodySize: options.maxRequestBodySize, ignoreRequestBody: options.ignoreRequestBody, }; }) satisfies IntegrationFn; @@ -85,7 +86,10 @@ export async function captureIncomingRequestBody(client: Client, request: Reques return; } - const maxRequestBodySize = integration.maxRequestBodySize; + // Integration-level option takes precedence; fall back to dataCollection.httpBodies + const maxRequestBodySize = + integration.maxRequestBodySize ?? + (client.getDataCollectionOptions().httpBodies.includes('incomingRequest') ? 'medium' : 'none'); if (maxRequestBodySize === 'none') { return; 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..a85584b382c9 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -24,7 +24,6 @@ import { defaultStackParser } from './vendor/stacktrace'; /** Get the default integrations for the Cloudflare SDK. */ export function getDefaultIntegrations(options: CloudflareOptions): Integration[] { - const sendDefaultPii = options.sendDefaultPii ?? false; 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 +37,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(), consoleIntegration(), ]; } diff --git a/packages/cloudflare/test/request.test.ts b/packages/cloudflare/test/request.test.ts index 2164989833b3..307bba57f98a 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -205,7 +205,7 @@ describe('withSentry', () => { expect(sentryEvent.contexts?.culture).toEqual({ timezone: 'UTC' }); }); - test('captures request body with default integration (medium size)', async () => { + test('does not capture request body by default (dataCollection.httpBodies is empty)', async () => { let sentryEvent: Event = {}; const context = createMockExecutionContext(); @@ -213,7 +213,36 @@ describe('withSentry', () => { { options: { ...MOCK_OPTIONS, - // Default integrations include httpServerIntegration with 'medium' default + beforeSend(event) { + sentryEvent = event; + return null; + }, + }, + request: new Request('https://example.com', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ username: 'test', data: 'value' }), + }), + context, + }, + () => { + SentryCore.captureMessage('request body'); + return new Response('test'); + }, + ); + + expect(sentryEvent.sdkProcessingMetadata?.normalizedRequest?.data).toBeUndefined(); + }); + + test('captures request body when dataCollection.httpBodies includes incomingRequest', async () => { + let sentryEvent: Event = {}; + const context = createMockExecutionContext(); + + await wrapRequestHandler( + { + options: { + ...MOCK_OPTIONS, + dataCollection: { httpBodies: ['incomingRequest'] }, beforeSend(event) { sentryEvent = event; return null; From 7945cb361faf9247e965c536153c274ae1fc67e8 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 1 Jun 2026 11:11:40 +0200 Subject: [PATCH 2/2] non breaking --- .../cloudflare/src/integrations/httpServer.ts | 16 ++-- packages/cloudflare/src/sdk.ts | 6 +- packages/cloudflare/test/request.test.ts | 78 +++++++++++++++---- 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/packages/cloudflare/src/integrations/httpServer.ts b/packages/cloudflare/src/integrations/httpServer.ts index 0beb462087cd..f9643278098b 100644 --- a/packages/cloudflare/src/integrations/httpServer.ts +++ b/packages/cloudflare/src/integrations/httpServer.ts @@ -14,11 +14,10 @@ export interface HttpServerIntegrationOptions { * Available options: * - `'none'`: No request bodies will be attached * - `'small'`: Request bodies up to 1,000 bytes will be attached - * - `'medium'`: Request bodies up to 10,000 bytes will be attached + * - `'medium'`: Request bodies up to 10,000 bytes will be attached (default) * - `'always'`: Request bodies will always be attached (up to 1MB limit) * - * When not set, falls back to `dataCollection.httpBodies`: if `'incomingRequest'` - * is listed, bodies are captured at `'medium'` size; otherwise no bodies are captured. + * @default 'medium' */ maxRequestBodySize?: MaxRequestBodySize; @@ -43,14 +42,14 @@ export interface HttpServerIntegrationOptions { interface HttpServerIntegrationInstance { name: string; - maxRequestBodySize: MaxRequestBodySize | undefined; + maxRequestBodySize: MaxRequestBodySize; ignoreRequestBody?: (url: string, request: Request) => boolean; } const _httpServerIntegration = ((options: HttpServerIntegrationOptions = {}): HttpServerIntegrationInstance => { return { name: INTEGRATION_NAME, - maxRequestBodySize: options.maxRequestBodySize, + maxRequestBodySize: options.maxRequestBodySize ?? 'medium', ignoreRequestBody: options.ignoreRequestBody, }; }) satisfies IntegrationFn; @@ -86,10 +85,9 @@ export async function captureIncomingRequestBody(client: Client, request: Reques return; } - // Integration-level option takes precedence; fall back to dataCollection.httpBodies - const maxRequestBodySize = - integration.maxRequestBodySize ?? - (client.getDataCollectionOptions().httpBodies.includes('incomingRequest') ? 'medium' : 'none'); + // 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') { return; diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index a85584b382c9..fb44121422e7 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -24,6 +24,10 @@ import { defaultStackParser } from './vendor/stacktrace'; /** 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`. + 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. @@ -37,7 +41,7 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[ fetchIntegration(), honoIntegration(), httpServerIntegration(), - requestDataIntegration(), + requestDataIntegration(cookiesEnabled ? undefined : { include: { cookies: false } }), consoleIntegration(), ]; } diff --git a/packages/cloudflare/test/request.test.ts b/packages/cloudflare/test/request.test.ts index 307bba57f98a..4e81bd254258 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -205,7 +205,10 @@ describe('withSentry', () => { expect(sentryEvent.contexts?.culture).toEqual({ timezone: 'UTC' }); }); - test('does not capture request body by default (dataCollection.httpBodies is empty)', async () => { + // 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(); @@ -213,6 +216,7 @@ describe('withSentry', () => { { options: { ...MOCK_OPTIONS, + // Default integrations include httpServerIntegration with 'medium' default beforeSend(event) { sentryEvent = event; return null; @@ -231,39 +235,85 @@ describe('withSentry', () => { }, ); - expect(sentryEvent.sdkProcessingMetadata?.normalizedRequest?.data).toBeUndefined(); + expect(sentryEvent.sdkProcessingMetadata?.normalizedRequest?.data).toEqual( + JSON.stringify({ username: 'test', data: 'value' }), + ); }); - test('captures request body when dataCollection.httpBodies includes incomingRequest', async () => { + // 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 = {}; - const context = createMockExecutionContext(); await wrapRequestHandler( { options: { ...MOCK_OPTIONS, - dataCollection: { httpBodies: ['incomingRequest'] }, beforeSend(event) { sentryEvent = event; return null; }, }, - request: new Request('https://example.com', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ username: 'test', data: 'value' }), - }), - context, + request: new Request('https://example.com', { headers: { cookie: 'foo=bar' } }), + context: createMockExecutionContext(), }, () => { - SentryCore.captureMessage('request body'); + SentryCore.captureMessage('cookies'); return new Response('test'); }, ); - expect(sentryEvent.sdkProcessingMetadata?.normalizedRequest?.data).toEqual( - JSON.stringify({ username: 'test', data: 'value' }), + 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 () => {