Skip to content

Commit 3496593

Browse files
committed
feat(core)!: Gate incoming HTTP body capture on dataCollection.httpBodies
1 parent f030305 commit 3496593

5 files changed

Lines changed: 82 additions & 17 deletions

File tree

packages/cloudflare/src/integrations/httpServer.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ export interface HttpServerIntegrationOptions {
4242

4343
interface HttpServerIntegrationInstance {
4444
name: string;
45-
maxRequestBodySize: MaxRequestBodySize;
45+
maxRequestBodySize: MaxRequestBodySize | undefined;
4646
ignoreRequestBody?: (url: string, request: Request) => boolean;
4747
}
4848

4949
const _httpServerIntegration = ((options: HttpServerIntegrationOptions = {}): HttpServerIntegrationInstance => {
5050
return {
5151
name: INTEGRATION_NAME,
52-
maxRequestBodySize: options.maxRequestBodySize ?? 'medium',
52+
maxRequestBodySize: options.maxRequestBodySize,
5353
ignoreRequestBody: options.ignoreRequestBody,
5454
};
5555
}) satisfies IntegrationFn;
@@ -85,11 +85,12 @@ export async function captureIncomingRequestBody(client: Client, request: Reques
8585
return;
8686
}
8787

88-
// TODO(v11): Gate incoming request body capture on `dataCollection.httpBodies` (capture only when
89-
// `'incomingRequest'` is listed) instead of defaulting to `'medium'`.
90-
const maxRequestBodySize = integration.maxRequestBodySize;
88+
const configuredBodySize = integration.maxRequestBodySize;
89+
const effectiveBodySize: MaxRequestBodySize =
90+
configuredBodySize ??
91+
(client.getDataCollectionOptions().httpBodies.includes('incomingRequest') ? 'medium' : 'none');
9192

92-
if (maxRequestBodySize === 'none') {
93+
if (effectiveBodySize === 'none') {
9394
return;
9495
}
9596

@@ -104,5 +105,5 @@ export async function captureIncomingRequestBody(client: Client, request: Reques
104105
}
105106

106107
const isolationScope = getIsolationScope();
107-
await captureBodyFromWinterCGRequest(request, isolationScope, maxRequestBodySize);
108+
await captureBodyFromWinterCGRequest(request, isolationScope, effectiveBodySize);
108109
}

packages/cloudflare/test/request.test.ts

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { beforeAll, beforeEach, describe, expect, onTestFinished, test, vi } fro
88
import { setAsyncLocalStorageAsyncContextStrategy } from '../src/async';
99
import type { CloudflareOptions } from '../src/client';
1010
import { CloudflareClient } from '../src/client';
11+
import { httpServerIntegration } from '../src/integrations/httpServer';
1112
import { wrapRequestHandler } from '../src/request';
1213

1314
const MOCK_OPTIONS: CloudflareOptions = {
@@ -206,18 +207,14 @@ describe('withSentry', () => {
206207
expect(sentryEvent.contexts?.culture).toEqual({ timezone: 'UTC' });
207208
});
208209

209-
// TODO(v11): Body capture should be gated on `dataCollection.httpBodies` (only capture when
210-
// `'incomingRequest'` is listed). Until then we keep the historical behavior of capturing
211-
// incoming request bodies by default at `'medium'`, consistent with the Node SDK.
212-
test('captures request body with default integration (medium size)', async () => {
210+
test('captures request body by default (all body types included by default)', async () => {
213211
let sentryEvent: Event = {};
214212
const context = createMockExecutionContext();
215213

216214
await wrapRequestHandler(
217215
{
218216
options: {
219217
...MOCK_OPTIONS,
220-
// Default integrations include httpServerIntegration with 'medium' default
221218
beforeSend(event) {
222219
sentryEvent = event;
223220
return null;
@@ -241,6 +238,69 @@ describe('withSentry', () => {
241238
);
242239
});
243240

241+
test('does not capture request body when dataCollection.httpBodies excludes incomingRequest', async () => {
242+
let sentryEvent: Event = {};
243+
const context = createMockExecutionContext();
244+
245+
await wrapRequestHandler(
246+
{
247+
options: {
248+
...MOCK_OPTIONS,
249+
dataCollection: { httpBodies: [] },
250+
beforeSend(event) {
251+
sentryEvent = event;
252+
return null;
253+
},
254+
},
255+
request: new Request('https://example.com', {
256+
method: 'POST',
257+
headers: { 'content-type': 'application/json' },
258+
body: JSON.stringify({ username: 'test', data: 'value' }),
259+
}),
260+
context,
261+
},
262+
() => {
263+
SentryCore.captureMessage('request body');
264+
return new Response('test');
265+
},
266+
);
267+
268+
expect(sentryEvent.sdkProcessingMetadata?.normalizedRequest?.data).toBeUndefined();
269+
});
270+
271+
test('explicit maxRequestBodySize overrides dataCollection.httpBodies', async () => {
272+
let sentryEvent: Event = {};
273+
const context = createMockExecutionContext();
274+
275+
await wrapRequestHandler(
276+
{
277+
options: {
278+
...MOCK_OPTIONS,
279+
integrations: [
280+
// httpBodies not set → would default to 'none', but explicit override wins
281+
httpServerIntegration({ maxRequestBodySize: 'medium' }),
282+
],
283+
beforeSend(event) {
284+
sentryEvent = event;
285+
return null;
286+
},
287+
},
288+
request: new Request('https://example.com', {
289+
method: 'POST',
290+
headers: { 'content-type': 'application/json' },
291+
body: JSON.stringify({ key: 'value' }),
292+
}),
293+
context,
294+
},
295+
() => {
296+
SentryCore.captureMessage('request body');
297+
return new Response('test');
298+
},
299+
);
300+
301+
expect(sentryEvent.sdkProcessingMetadata?.normalizedRequest?.data).toEqual(JSON.stringify({ key: 'value' }));
302+
});
303+
244304
// TODO(v11): Cookies should be attached (subject to denylist filtering) by default. Until then we keep the
245305
// historical Cloudflare behavior of not attaching cookies unless the user explicitly opts in.
246306
test('does not capture cookies by default', async () => {

packages/core/src/integrations/http/server-subscription.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,18 @@ export function instrumentServer(options: HttpInstrumentationOptions, server: Ht
108108
const url = request.url || '/';
109109
const normalizedRequest = httpRequestToRequestData(request);
110110
const {
111-
maxRequestBodySize = 'medium',
111+
maxRequestBodySize: configuredBodySize,
112112
ignoreRequestBody,
113113
sessions = true,
114114
sessionFlushingDelayMS = 60_000,
115115
} = options;
116116

117-
if (maxRequestBodySize !== 'none' && !ignoreRequestBody?.(url, request)) {
118-
patchRequestToCaptureBody(request, isolationScope, maxRequestBodySize, INTEGRATION_NAME);
117+
const effectiveBodySize =
118+
configuredBodySize ??
119+
(client.getDataCollectionOptions().httpBodies.includes('incomingRequest') ? 'medium' : 'none');
120+
121+
if (effectiveBodySize !== 'none' && !ignoreRequestBody?.(url, request)) {
122+
patchRequestToCaptureBody(request, isolationScope, effectiveBodySize, INTEGRATION_NAME);
119123
}
120124

121125
// Update the isolation scope, isolate this request

packages/deno/src/integrations/http.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const _denoHttpIntegration = ((options: DenoHttpIntegrationOptions = {}) => {
9999
spans: options.spans,
100100
ignoreStaticAssets: options.ignoreStaticAssets,
101101
ignoreIncomingRequests: options.ignoreIncomingRequests,
102-
maxRequestBodySize: options.maxRequestBodySize ?? 'medium',
102+
maxRequestBodySize: options.maxRequestBodySize,
103103
ignoreRequestBody: options.ignoreRequestBody,
104104
onSpanCreated: options.onIncomingSpanCreated,
105105
onSpanEnd: options.onIncomingSpanEnd,

packages/node/src/integrations/http/httpServerIntegration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ const _httpServerIntegration = ((options: HttpServerIntegrationOptions = {}) =>
8383
const _options = {
8484
sessions: options.sessions ?? true,
8585
sessionFlushingDelayMS: options.sessionFlushingDelayMS ?? 60_000,
86-
maxRequestBodySize: options.maxRequestBodySize ?? 'medium',
86+
maxRequestBodySize: options.maxRequestBodySize,
8787
// Server spans are created by `httpServerSpansIntegration` via the
8888
// `httpServerRequest` client event + `_startSpanCallback`, not by the
8989
// core subscription helper. Explicitly opt out so the helper does not

0 commit comments

Comments
 (0)