Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ it('Basic error in fetch handler', async ({ signal }) => {
],
},
request: {
cookies: {},
headers: expect.any(Object),
method: 'GET',
url: expect.any(String),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ it('Tracing headers', async ({ signal }) => {
},
],
request: {
cookies: {},
headers: expect.any(Object),
method: 'GET',
url: expect.any(String),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
7 changes: 1 addition & 6 deletions packages/cloudflare/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand Down
30 changes: 27 additions & 3 deletions packages/cloudflare/test/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/src/edge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 4 additions & 5 deletions packages/vercel-edge/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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`
Expand All @@ -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) {
Expand Down
42 changes: 40 additions & 2 deletions packages/vercel-edge/test/sdk.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
Loading