Skip to content

Commit d52374e

Browse files
authored
ref(tracing): Only add user_id to DSC if sendDefaultPii is true (#5344)
Add the check if `sendDefaultPii` is set to `true` before adding the `user_id` when populating DSC. Additionally, add some tests to check for the changed behaviour. Conforms with Sentry Dev Spec for handling sensitive data in DSC: https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#handling-sensitive-data-and-pii
1 parent 259b4f8 commit d52374e

File tree

10 files changed

+182
-6
lines changed

10 files changed

+182
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { Integrations } from '@sentry/tracing';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [new Integrations.BrowserTracing({ tracingOrigins: [/.*/] })],
9+
environment: 'production',
10+
tracesSampleRate: 1,
11+
debug: true,
12+
});
13+
14+
Sentry.configureScope(scope => {
15+
scope.setUser({ id: 'user123', segment: 'segmentB' });
16+
scope.setTransactionName('testTransactionDSC');
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { expect } from '@playwright/test';
2+
import { EventEnvelopeHeaders } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../utils/fixtures';
5+
import { envelopeHeaderRequestParser, getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
6+
7+
sentryTest(
8+
'should not send user_id in DSC data in trace envelope header if sendDefaultPii option is not set',
9+
async ({ getLocalTestPath, page }) => {
10+
const url = await getLocalTestPath({ testDir: __dirname });
11+
12+
const envHeader = await getFirstSentryEnvelopeRequest<EventEnvelopeHeaders>(page, url, envelopeHeaderRequestParser);
13+
14+
expect(envHeader.trace).toBeDefined();
15+
expect(envHeader.trace).toEqual({
16+
environment: 'production',
17+
transaction: expect.stringContaining('index.html'),
18+
user_segment: 'segmentB',
19+
sample_rate: '1',
20+
trace_id: expect.any(String),
21+
public_key: 'public',
22+
});
23+
},
24+
);

packages/integration-tests/suites/tracing/envelope-header/init.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Sentry.init({
88
integrations: [new Integrations.BrowserTracing({ tracingOrigins: [/.*/] })],
99
environment: 'production',
1010
tracesSampleRate: 1,
11+
sendDefaultPii: true,
1112
debug: true,
1213
});
1314

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as Sentry from '@sentry/node';
2+
import * as Tracing from '@sentry/tracing';
3+
import cors from 'cors';
4+
import express from 'express';
5+
import http from 'http';
6+
7+
const app = express();
8+
9+
export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } };
10+
11+
Sentry.init({
12+
dsn: 'https://[email protected]/1337',
13+
release: '1.0',
14+
environment: 'prod',
15+
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
16+
tracesSampleRate: 1.0,
17+
});
18+
19+
Sentry.setUser({ id: 'user123', segment: 'SegmentA' });
20+
21+
app.use(Sentry.Handlers.requestHandler());
22+
app.use(Sentry.Handlers.tracingHandler());
23+
24+
app.use(cors());
25+
26+
app.get('/test/express', (_req, res) => {
27+
const transaction = Sentry.getCurrentHub().getScope()?.getTransaction();
28+
if (transaction) {
29+
transaction.traceId = '86f39e84263a4de99c326acab3bfe3bd';
30+
}
31+
const headers = http.get('http://somewhere.not.sentry/').getHeaders();
32+
33+
// Responding with the headers outgoing request headers back to the assertions.
34+
res.send({ test_data: headers });
35+
});
36+
37+
app.use(Sentry.Handlers.errorHandler());
38+
39+
export default app;

packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,31 @@ import { getAPIResponse, runServer } from '../../../../utils/index';
44
import { TestAPIResponse } from '../server';
55

66
test('should attach a `baggage` header to an outgoing request.', async () => {
7-
const url = await runServer(__dirname, `${path.resolve(__dirname, '..')}/server.ts`);
7+
const url = await runServer(__dirname, `${path.resolve(__dirname, '.')}/server.ts`);
88

99
const response = (await getAPIResponse(new URL(`${url}/express`))) as TestAPIResponse;
1010

1111
expect(response).toBeDefined();
1212
expect(response).toMatchObject({
1313
test_data: {
1414
host: 'somewhere.not.sentry',
15-
baggage: expect.stringMatching('sentry-environment=prod,sentry-release=1.0'),
15+
baggage:
16+
'sentry-environment=prod,sentry-release=1.0,sentry-transaction=GET%20%2Ftest%2Fexpress,sentry-user_segment=SegmentA' +
17+
',sentry-public_key=public,sentry-trace_id=86f39e84263a4de99c326acab3bfe3bd,sentry-sample_rate=1',
18+
},
19+
});
20+
});
21+
22+
test('Does not include user_id in baggage if sendDefaultPii is not set', async () => {
23+
const url = await runServer(__dirname, `${path.resolve(__dirname, '.')}/server.ts`);
24+
25+
const response = (await getAPIResponse(new URL(`${url}/express`))) as TestAPIResponse;
26+
27+
expect(response).toBeDefined();
28+
expect(response).toMatchObject({
29+
test_data: {
30+
host: 'somewhere.not.sentry',
31+
baggage: expect.not.stringContaining('sentry-user_id'),
1632
},
1733
});
1834
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import * as Sentry from '@sentry/node';
2+
import * as Tracing from '@sentry/tracing';
3+
import cors from 'cors';
4+
import express from 'express';
5+
import http from 'http';
6+
7+
const app = express();
8+
9+
export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } };
10+
11+
Sentry.init({
12+
dsn: 'https://[email protected]/1337',
13+
release: '1.0',
14+
environment: 'prod',
15+
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
16+
tracesSampleRate: 1.0,
17+
sendDefaultPii: true,
18+
});
19+
20+
Sentry.setUser({ id: 'user123', segment: 'SegmentA' });
21+
22+
app.use(Sentry.Handlers.requestHandler());
23+
app.use(Sentry.Handlers.tracingHandler());
24+
25+
app.use(cors());
26+
27+
app.get('/test/express', (_req, res) => {
28+
const transaction = Sentry.getCurrentHub().getScope()?.getTransaction();
29+
if (transaction) {
30+
transaction.traceId = '86f39e84263a4de99c326acab3bfe3bd';
31+
}
32+
const headers = http.get('http://somewhere.not.sentry/').getHeaders();
33+
34+
// Responding with the headers outgoing request headers back to the assertions.
35+
res.send({ test_data: headers });
36+
});
37+
38+
app.use(Sentry.Handlers.errorHandler());
39+
40+
export default app;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as path from 'path';
2+
3+
import { getAPIResponse, runServer } from '../../../../utils/index';
4+
import { TestAPIResponse } from '../server';
5+
6+
test('Includes user_id in baggage if sendDefaultPii is set to true', async () => {
7+
const url = await runServer(__dirname, `${path.resolve(__dirname, '.')}/server.ts`);
8+
9+
const response = (await getAPIResponse(new URL(`${url}/express`))) as TestAPIResponse;
10+
11+
expect(response).toBeDefined();
12+
expect(response).toMatchObject({
13+
test_data: {
14+
host: 'somewhere.not.sentry',
15+
baggage: expect.stringContaining('sentry-user_id=user123'),
16+
},
17+
});
18+
});

packages/node/test/integrations/http.test.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,21 @@ import * as nock from 'nock';
1111
import { Breadcrumb } from '../../src';
1212
import { NodeClient } from '../../src/client';
1313
import { Http as HttpIntegration } from '../../src/integrations/http';
14+
import { NodeClientOptions } from '../../src/types';
1415
import { getDefaultNodeClientOptions } from '../helper/node-client-options';
1516

1617
const NODE_VERSION = parseSemver(process.versions.node);
1718

1819
describe('tracing', () => {
19-
function createTransactionOnScope() {
20+
function createTransactionOnScope(customOptions: Partial<NodeClientOptions> = {}) {
2021
const options = getDefaultNodeClientOptions({
2122
dsn: 'https://[email protected]/12312012',
2223
tracesSampleRate: 1.0,
2324
integrations: [new HttpIntegration({ tracing: true })],
2425
release: '1.0.0',
2526
environment: 'production',
27+
sendDefaultPii: true,
28+
...customOptions,
2629
});
2730
const hub = new Hub(new NodeClient(options));
2831
addExtensionMethods();
@@ -133,6 +136,23 @@ describe('tracing', () => {
133136
);
134137
});
135138

139+
it('does not add the user_id to the baggage header if sendDefaultPii is set to false', async () => {
140+
nock('http://dogs.are.great').get('/').reply(200);
141+
142+
createTransactionOnScope({ sendDefaultPii: false });
143+
144+
const request = http.get({ host: 'http://dogs.are.great/', headers: { baggage: 'dog=great' } });
145+
const baggageHeader = request.getHeader('baggage') as string;
146+
147+
expect(baggageHeader).toBeDefined();
148+
expect(typeof baggageHeader).toEqual('string');
149+
expect(baggageHeader).toEqual(
150+
'dog=great,sentry-environment=production,sentry-release=1.0.0,sentry-transaction=dogpark,' +
151+
'sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,' +
152+
'sentry-trace_id=12312012123120121231201212312012,sentry-sample_rate=1',
153+
);
154+
});
155+
136156
it("doesn't attach the sentry-trace header to outgoing sentry requests", () => {
137157
nock('http://squirrelchasers.ingest.sentry.io').get('/api/12312012/store/').reply(200);
138158

packages/tracing/src/transaction.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ export class Transaction extends SpanClass implements TransactionInterface {
237237
environment,
238238
release,
239239
transaction: this.name,
240-
user_id,
240+
...(hub.shouldSendDefaultPii() && { user_id }),
241241
user_segment,
242242
public_key,
243243
trace_id: this.traceId,

packages/tracing/test/span.test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -434,11 +434,12 @@ describe('Span', () => {
434434
hub,
435435
);
436436

437-
const hubSpy = jest.spyOn(hub.getClient()!, 'getOptions');
437+
const getOptionsSpy = jest.spyOn(hub.getClient()!, 'getOptions');
438438

439439
const baggage = transaction.getBaggage();
440440

441-
expect(hubSpy).toHaveBeenCalledTimes(1);
441+
// this is called twice because hub.shouldSendDefaultPii also calls getOptions()
442+
expect(getOptionsSpy).toHaveBeenCalledTimes(2);
442443
expect(baggage && isSentryBaggageEmpty(baggage)).toBe(false);
443444
expect(baggage && getSentryBaggageItems(baggage)).toStrictEqual({
444445
release: '1.0.1',

0 commit comments

Comments
 (0)