diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 3f365f111ed9..ef5635f98541 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -476,7 +476,7 @@ export abstract class BaseClient implements Client { return null; } - const normalized = { + const normalized: Event = { ...event, ...(event.breadcrumbs && { breadcrumbs: event.breadcrumbs.map(b => ({ @@ -496,6 +496,7 @@ export abstract class BaseClient implements Client { extra: normalize(event.extra, depth, maxBreadth), }), }; + // event.contexts.trace stores information about a Transaction. Similarly, // event.spans[] stores information about child Spans. Given that a // Transaction is conceptually a Span, normalization should apply to both @@ -504,8 +505,24 @@ export abstract class BaseClient implements Client { // so this block overwrites the normalized event to add back the original // Transaction information prior to normalization. if (event.contexts && event.contexts.trace) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + normalized.contexts = {}; normalized.contexts.trace = event.contexts.trace; + + // event.contexts.trace.data may contain circular/dangerous data so we need to normalize it + if (event.contexts.trace.data) { + normalized.contexts.trace.data = normalize(event.contexts.trace.data, depth, maxBreadth); + } + } + + // event.spans[].data may contain circular/dangerous data so we need to normalize it + if (event.spans) { + normalized.spans = event.spans.map(span => { + // We cannot use the spread operator here because `toJSON` on `span` is non-enumerable + if (span.data) { + span.data = normalize(span.data, depth, maxBreadth); + } + return span; + }); } normalized.sdkProcessingMetadata = { ...normalized.sdkProcessingMetadata, baseClientNormalized: true }; diff --git a/packages/integration-tests/suites/public-api/startTransaction/circular_data/subject.js b/packages/integration-tests/suites/public-api/startTransaction/circular_data/subject.js new file mode 100644 index 000000000000..50f8cef000be --- /dev/null +++ b/packages/integration-tests/suites/public-api/startTransaction/circular_data/subject.js @@ -0,0 +1,11 @@ +const chicken = {}; +const egg = { contains: chicken }; +chicken.lays = egg; + +const circularObject = chicken; + +const transaction = Sentry.startTransaction({ name: 'circular_object_test_transaction', data: circularObject }); +const span = transaction.startChild({ op: 'circular_object_test_span', data: circularObject }); + +span.finish(); +transaction.finish(); diff --git a/packages/integration-tests/suites/public-api/startTransaction/circular_data/test.ts b/packages/integration-tests/suites/public-api/startTransaction/circular_data/test.ts new file mode 100644 index 000000000000..7ad0ec532fb7 --- /dev/null +++ b/packages/integration-tests/suites/public-api/startTransaction/circular_data/test.ts @@ -0,0 +1,26 @@ +import { expect } from '@playwright/test'; +import { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; + +sentryTest('should be able to handle circular data', async ({ getLocalTestPath, page }) => { + const url = await getLocalTestPath({ testDir: __dirname }); + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.type).toBe('transaction'); + expect(eventData.transaction).toBe('circular_object_test_transaction'); + + expect(eventData.contexts).toMatchObject({ + trace: { + data: { lays: { contains: '[Circular ~]' } }, + }, + }); + + expect(eventData?.spans?.[0]).toMatchObject({ + data: { lays: { contains: '[Circular ~]' } }, + op: 'circular_object_test_span', + }); + + await new Promise(resolve => setTimeout(resolve, 2000)); +}); diff --git a/packages/integration-tests/suites/public-api/startTransaction/init.js b/packages/integration-tests/suites/public-api/startTransaction/init.js index b326cc489bde..0aadc7c39b84 100644 --- a/packages/integration-tests/suites/public-api/startTransaction/init.js +++ b/packages/integration-tests/suites/public-api/startTransaction/init.js @@ -7,4 +7,5 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1.0, + normalizeDepth: 10, });