Skip to content

Commit 236c0ce

Browse files
JPeer264claude
andauthored
fix(browser): Apply urlQueryParams to breadcrumb and element timing URLs (#25289)
follow up to #25262 This applies `dataCollection.urlQueryParams` to the URLs in the fetch, XHR and navigation breadcrumbs and to the `ui.element.url` attribute of `elementTimingIntegration`. These still sent sensitive query values such as `?jwt=...` to Sentry. ## Sentry events Before (11.6.0), `jwt=sensitive-jwt-value` in the breadcrumbs: https://sentry-sdks.sentry.io/issues/7787761489/events/2bfc6630501c4314bf84b884a4afb1e9/ After (this PR), `jwt=[Filtered]`: https://sentry-sdks.sentry.io/issues/7787761463/events/6ae067a147b84e6eb33553ae7ce217c7/ Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ee04661 commit 236c0ce

9 files changed

Lines changed: 106 additions & 8 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fetch('http://sentry-test.io/foo?token=sensitive-token-value&page=1').then(() => {
2+
Sentry.captureException('test error');
3+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/core';
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';
5+
6+
sentryTest('filters sensitive query params in the fetch breadcrumb URL', async ({ getLocalTestUrl, page }) => {
7+
const url = await getLocalTestUrl({ testDir: __dirname });
8+
9+
await page.route('**/foo?*', route => route.fulfill({ status: 200, body: '{}' }));
10+
11+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
12+
13+
expect(eventData.breadcrumbs).toEqual([
14+
{
15+
timestamp: expect.any(Number),
16+
category: 'fetch',
17+
type: 'http',
18+
data: {
19+
method: 'GET',
20+
status_code: 200,
21+
url: 'http://sentry-test.io/foo?token=[Filtered]&page=1',
22+
},
23+
},
24+
]);
25+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
history.pushState({}, '', '/foo?jwt=sensitive-jwt-value&a=1');
2+
history.pushState({}, '', '/bar');
3+
4+
Sentry.captureException('test exception');
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/browser';
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';
5+
6+
sentryTest('filters sensitive query params in navigation breadcrumbs', async ({ getLocalTestUrl, page }) => {
7+
const url = await getLocalTestUrl({ testDir: __dirname });
8+
9+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
10+
11+
expect(eventData.breadcrumbs).toEqual([
12+
{
13+
category: 'navigation',
14+
data: {
15+
from: '/index.html',
16+
to: '/foo?jwt=[Filtered]&a=1',
17+
},
18+
timestamp: expect.any(Number),
19+
},
20+
{
21+
category: 'navigation',
22+
data: {
23+
from: '/foo?jwt=[Filtered]&a=1',
24+
to: '/bar',
25+
},
26+
timestamp: expect.any(Number),
27+
},
28+
]);
29+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const xhr = new XMLHttpRequest();
2+
3+
xhr.open('GET', 'http://sentry-test.io/foo?token=sensitive-token-value&page=1');
4+
xhr.send();
5+
6+
xhr.addEventListener('readystatechange', function () {
7+
if (xhr.readyState === 4) {
8+
Sentry.captureException('test error');
9+
}
10+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/core';
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';
5+
6+
sentryTest('filters sensitive query params in the XHR breadcrumb URL', async ({ getLocalTestUrl, page }) => {
7+
const url = await getLocalTestUrl({ testDir: __dirname });
8+
9+
await page.route('**/foo?*', route => route.fulfill({ status: 200, body: '{}' }));
10+
11+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
12+
13+
expect(eventData.breadcrumbs).toEqual([
14+
{
15+
timestamp: expect.any(Number),
16+
category: 'xhr',
17+
type: 'http',
18+
data: {
19+
method: 'GET',
20+
status_code: 200,
21+
url: 'http://sentry-test.io/foo?token=[Filtered]&page=1',
22+
},
23+
},
24+
]);
25+
});

‎packages/browser-utils/src/performance/elementTiming.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { IntegrationFn } from '@sentry/core';
2-
import { browserPerformanceTimeOrigin, defineIntegration, metrics } from '@sentry/core';
2+
import { browserPerformanceTimeOrigin, defineIntegration, filterCollectedUrl, metrics } from '@sentry/core';
33
import { addPerformanceInstrumentationHandler } from '../instrumentation/performanceObserver';
44
import { getBrowserPerformanceAPI } from './utils';
55

@@ -58,7 +58,7 @@ const _elementTimingIntegration = (() => {
5858
}
5959

6060
if (elementEntry.url) {
61-
metricAttributes['ui.element.url'] = elementEntry.url;
61+
metricAttributes['ui.element.url'] = filterCollectedUrl(elementEntry.url);
6262
}
6363

6464
if (elementEntry.naturalWidth) {

‎packages/browser-utils/test/performance/elementTiming.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('elementTimingIntegration', () => {
9898
identifier: 'hero-image',
9999
id: 'img1',
100100
element: { tagName: 'IMG' },
101-
url: 'https://example.com/hero.jpg',
101+
url: 'https://example.com/hero.jpg?token=abc&w=1920',
102102
naturalWidth: 1920,
103103
naturalHeight: 1080,
104104
} as unknown as PerformanceEntry,
@@ -112,7 +112,7 @@ describe('elementTimingIntegration', () => {
112112
'ui.element.paint_type': 'image-paint',
113113
'ui.element.id': 'img1',
114114
'ui.element.type': 'img',
115-
'ui.element.url': 'https://example.com/hero.jpg',
115+
'ui.element.url': 'https://example.com/hero.jpg?token=[Filtered]&w=1920',
116116
'ui.element.width': 1920,
117117
'ui.element.height': 1080,
118118
};

‎packages/browser/src/integrations/breadcrumbs.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
addFetchInstrumentationHandler,
1515
debug,
1616
defineIntegration,
17+
filterCollectedUrl,
1718
getBreadcrumbLogLevelFromHttpStatusCode,
1819
getClient,
1920
getEventDescription,
@@ -192,7 +193,7 @@ function _getXhrBreadcrumbHandler(client: Client): (handlerData: HandlerDataXhr)
192193

193194
const data: XhrBreadcrumbData = {
194195
method,
195-
url,
196+
url: filterCollectedUrl(url),
196197
status_code,
197198
};
198199

@@ -247,7 +248,7 @@ function _getFetchBreadcrumbHandler(client: Client): (handlerData: HandlerDataFe
247248

248249
const breadcrumb = {
249250
category: 'fetch',
250-
data: handlerData.fetchData,
251+
data: { ...handlerData.fetchData, url: filterCollectedUrl(handlerData.fetchData.url) },
251252
level: 'error',
252253
type: 'http',
253254
} satisfies Breadcrumb;
@@ -259,6 +260,7 @@ function _getFetchBreadcrumbHandler(client: Client): (handlerData: HandlerDataFe
259260
const response = handlerData.response as Response | undefined;
260261
const data: FetchBreadcrumbData = {
261262
...handlerData.fetchData,
263+
url: filterCollectedUrl(handlerData.fetchData.url),
262264
status_code: response?.status,
263265
};
264266

@@ -315,8 +317,8 @@ function _getHistoryBreadcrumbHandler(client: Client): (handlerData: HandlerData
315317
addBreadcrumb({
316318
category: 'navigation',
317319
data: {
318-
from,
319-
to,
320+
from: filterCollectedUrl(from),
321+
to: filterCollectedUrl(to),
320322
},
321323
});
322324
};

0 commit comments

Comments
 (0)