Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions packages/cloudflare/src/integrations/httpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/cloudflare/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function wrapRequestHandler(
attributes,
httpHeadersToSpanAttributes(
winterCGHeadersToDict(request.headers),
getClient()?.getOptions().sendDefaultPii ?? false,
getClient()?.getDataCollectionOptions() ?? false,
),
);

Expand Down
4 changes: 1 addition & 3 deletions packages/cloudflare/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

/** 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.
Expand All @@ -38,11 +37,10 @@
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(),
];
}

Check warning on line 43 in packages/cloudflare/src/sdk.ts

View check run for this annotation

@sentry/warden / warden: security-review

Auth and session cookies now captured in Sentry events by default after removing explicit cookie exclusion

Removing the explicit `{ include: { cookies: false } }` guard from `requestDataIntegration()` causes all HTTP cookies — including session tokens, auth tokens, and CSRF cookies — to be captured in Sentry error events by default, because the new default `dataCollection.cookies` value (`{ deny: PII_HEADER_SNIPPETS }`) evaluates to `true` in the `include.cookies` check and the event path in `extractNormalizedRequestData` performs no further deny-list filtering.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auth and session cookies now captured in Sentry events by default after removing explicit cookie exclusion

Removing the explicit { include: { cookies: false } } guard from requestDataIntegration() causes all HTTP cookies — including session tokens, auth tokens, and CSRF cookies — to be captured in Sentry error events by default, because the new default dataCollection.cookies value ({ deny: PII_HEADER_SNIPPETS }) evaluates to true in the include.cookies check and the event path in extractNormalizedRequestData performs no further deny-list filtering.

Evidence
  • requestDataIntegration() (no options) calls resolveIncludeAndDataCollection(client) in packages/core/src/integrations/requestdata.ts.
  • With no dataCollection or sendDefaultPii set, client.getDataCollectionOptions() returns cookies: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] } — patterns for IP headers, not cookie names.
  • include.cookies = dataCollection.cookies !== false evaluates to true because { deny: [...] } !== false.
  • extractNormalizedRequestData then unconditionally executes requestData.cookies = parseCookie(headers.cookie) when include.cookies is true, adding all cookies to event.request.cookies with no deny-list filtering.
  • Previously requestDataIntegration({ include: { cookies: false } }) short-circuited this path entirely; the SENSITIVE_COOKIE_NAME_SNIPPETS filtering only applies in the span/httpHeadersToSpanAttributes path, not in the event processing path.

Identified by Warden security-review · EBZ-JZ3


/**
* Initializes the cloudflare SDK.
Expand Down
33 changes: 31 additions & 2 deletions packages/cloudflare/test/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,44 @@ 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();

await wrapRequestHandler(
{
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;
Expand Down
Loading