Skip to content

Commit 8ed224c

Browse files
authored
feat(browser): Migrate sendDefaultPii to dataCollection in browser packages (#21097)
- `browser/src/client.ts`: Migrate to `dataCollection` for `infer_ip` relay setting and `addAutoIpAddressToSession` listener - `browser/src/integrations/httpclient.ts`: Split single `sendDefaultPii` gate into granular `cookies`, `httpHeaders.request`, `httpHeaders.response` checks with new collect behavior filtering. Includes a backwards-compat guard so legacy `sendDefaultPii` users see no behavioral change (deferred to v11). - `browser-utils/src/metrics/utils.ts`: Migrate to `dataCollection` for `client.address` span attribute --- **Note on backwards compatibility:** When `dataCollection` is not explicitly set by the user, the httpclient integration falls back to the old `sendDefaultPii` boolean gate (all-or-nothing). This avoids silently sending more data for existing users. When `dataCollection` is explicitly set, the granular filtering is applied. (#21094) closes #20929
1 parent 556bcb3 commit 8ed224c

20 files changed

Lines changed: 504 additions & 58 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { httpClientIntegration } from '@sentry/browser';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
8+
integrations: [httpClientIntegration()],
9+
tracesSampleRate: 1,
10+
dataCollection: {
11+
cookies: true,
12+
httpHeaders: { request: true, response: true },
13+
},
14+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fetch('http://sentry-test.io/foo', {
2+
method: 'GET',
3+
credentials: 'include',
4+
headers: {
5+
Accept: 'application/json',
6+
'Content-Type': 'application/json',
7+
Cache: 'no-cache',
8+
},
9+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/core';
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
import { envelopeRequestParser, waitForErrorRequest } from '../../../../../utils/helpers';
5+
6+
sentryTest(
7+
'should capture request and response headers when using dataCollection options',
8+
async ({ getLocalTestUrl, page }) => {
9+
const url = await getLocalTestUrl({ testDir: __dirname });
10+
11+
await page.route('**/foo', route => {
12+
return route.fulfill({
13+
status: 500,
14+
body: JSON.stringify({
15+
error: {
16+
message: 'Internal Server Error',
17+
},
18+
}),
19+
headers: {
20+
'Content-Type': 'text/html',
21+
},
22+
});
23+
});
24+
25+
const req = await Promise.all([waitForErrorRequest(page), page.goto(url)]).then(([r]) => r);
26+
const eventData = envelopeRequestParser<Event>(req);
27+
28+
expect(eventData.exception?.values).toHaveLength(1);
29+
30+
expect(eventData).toMatchObject({
31+
message: 'HTTP Client Error with status code: 500',
32+
request: {
33+
url: 'http://sentry-test.io/foo',
34+
method: 'GET',
35+
headers: {
36+
accept: 'application/json',
37+
cache: 'no-cache',
38+
'content-type': 'application/json',
39+
},
40+
},
41+
contexts: {
42+
response: {
43+
status_code: 500,
44+
headers: {
45+
'content-type': 'text/html',
46+
},
47+
},
48+
},
49+
});
50+
},
51+
);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { httpClientIntegration } from '@sentry/browser';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
8+
integrations: [httpClientIntegration()],
9+
tracesSampleRate: 1,
10+
dataCollection: {
11+
cookies: false,
12+
httpHeaders: { request: false, response: false },
13+
},
14+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fetch('http://sentry-test.io/foo', {
2+
method: 'GET',
3+
credentials: 'include',
4+
headers: {
5+
Accept: 'application/json',
6+
'Content-Type': 'application/json',
7+
Cache: 'no-cache',
8+
},
9+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/core';
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
import { envelopeRequestParser, waitForErrorRequest } from '../../../../../utils/helpers';
5+
6+
sentryTest(
7+
'should not capture headers or cookies when dataCollection disables them',
8+
async ({ getLocalTestUrl, page }) => {
9+
const url = await getLocalTestUrl({ testDir: __dirname });
10+
11+
await page.route('**/foo', route => {
12+
return route.fulfill({
13+
status: 500,
14+
body: JSON.stringify({
15+
error: {
16+
message: 'Internal Server Error',
17+
},
18+
}),
19+
headers: {
20+
'Content-Type': 'text/html',
21+
},
22+
});
23+
});
24+
25+
const req = await Promise.all([waitForErrorRequest(page), page.goto(url)]).then(([r]) => r);
26+
const eventData = envelopeRequestParser<Event>(req);
27+
28+
expect(eventData.exception?.values).toHaveLength(1);
29+
expect(eventData.message).toBe('HTTP Client Error with status code: 500');
30+
31+
// Request URL and method are always present
32+
expect(eventData.request?.url).toBe('http://sentry-test.io/foo');
33+
expect(eventData.request?.method).toBe('GET');
34+
35+
// Request headers set in subject.js should not be captured
36+
expect(eventData.request?.headers?.accept).toBeUndefined();
37+
expect(eventData.request?.headers?.cache).toBeUndefined();
38+
expect(eventData.request?.headers?.['content-type']).toBeUndefined();
39+
expect(eventData.request?.cookies).toBeUndefined();
40+
41+
// Response headers should not be captured
42+
expect(eventData.contexts?.response?.headers?.['content-type']).toBeUndefined();
43+
expect(eventData.contexts?.response?.cookies).toBeUndefined();
44+
},
45+
);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { httpClientIntegration } from '@sentry/browser';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
8+
integrations: [httpClientIntegration()],
9+
tracesSampleRate: 1,
10+
dataCollection: {
11+
cookies: false,
12+
httpHeaders: { request: true, response: true },
13+
},
14+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fetch('http://sentry-test.io/foo', {
2+
method: 'GET',
3+
credentials: 'include',
4+
headers: {
5+
Accept: 'application/json',
6+
'Content-Type': 'application/json',
7+
Cache: 'no-cache',
8+
},
9+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/core';
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
import { envelopeRequestParser, waitForErrorRequest } from '../../../../../utils/helpers';
5+
6+
sentryTest(
7+
'should capture headers but not cookies when cookies are disabled in dataCollection',
8+
async ({ getLocalTestUrl, page }) => {
9+
const url = await getLocalTestUrl({ testDir: __dirname });
10+
11+
await page.route('**/foo', route => {
12+
return route.fulfill({
13+
status: 500,
14+
body: JSON.stringify({
15+
error: {
16+
message: 'Internal Server Error',
17+
},
18+
}),
19+
headers: {
20+
'Content-Type': 'text/html',
21+
},
22+
});
23+
});
24+
25+
const req = await Promise.all([waitForErrorRequest(page), page.goto(url)]).then(([r]) => r);
26+
const eventData = envelopeRequestParser<Event>(req);
27+
28+
expect(eventData.exception?.values).toHaveLength(1);
29+
30+
// Headers should be present
31+
expect(eventData.request?.headers).toMatchObject({
32+
accept: 'application/json',
33+
cache: 'no-cache',
34+
'content-type': 'application/json',
35+
});
36+
37+
expect(eventData.contexts?.response?.headers).toMatchObject({
38+
'content-type': 'text/html',
39+
});
40+
41+
// Cookies should not be present
42+
expect(eventData.request?.cookies).toBeUndefined();
43+
expect(eventData.contexts?.response?.cookies).toBeUndefined();
44+
},
45+
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
fetch('http://sentry-test.io/foo', {
2+
method: 'GET',
3+
credentials: 'include',
4+
headers: {
5+
Accept: 'application/json',
6+
Authorization: 'Bearer super-secret-token-123',
7+
'Content-Type': 'application/json',
8+
'X-API-Key': 'my-api-key-456',
9+
'X-Custom-Header': 'safe-value',
10+
},
11+
});

0 commit comments

Comments
 (0)