Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@
## Unreleased

### Important Changes

- **fix(browser): Ensure IP address is only inferred by Relay if `sendDefaultPii` is `true`** ([#???](https://github.com/getsentry/sentry-react-native/pull/??))

This release includes a fix for a [behaviour change](https://docs.sentry.io/platforms/javascript/migration/v8-to-v9/#behavior-changes)
that was originally introduced with v9 of the JavaScript SDK: User IP Addresses should only be added to Sentry events automatically,
if `sendDefaultPii` was set to `true`.

However, the change in v9 required further internal adjustment, which should have been included in v10 of the SDK.
To avoid making a major bump, the fix was patched on the current version and not by bumping to V10.
There is _no API_ breakage involved and hence it is safe to update.
However, after updating the SDK, events (errors, traces, replays, etc.) sent from the browser, will only include
user IP addresses, if you set `sendDefaultPii: true` in your `Sentry.init` options.

We apologize for any inconvenience caused!

## Features

- Logs now contains more attributes like release, os and device information ([#5032](https://github.com/getsentry/sentry-react-native/pull/5032))
Expand Down
39 changes: 34 additions & 5 deletions packages/core/src/js/integrations/sdkinfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { isExpoGo, notWeb } from '../utils/environment';
import { SDK_NAME, SDK_PACKAGE_NAME, SDK_VERSION } from '../version';
import { NATIVE } from '../wrapper';

// TODO: Remove this on JS V10.
interface IpPatchedSdkInfo extends SdkInfoType {
settings?: {
infer_ip?: 'auto' | 'never';
};
}


const INTEGRATION_NAME = 'SdkInfo';

type DefaultSdkInfo = Pick<Required<SdkInfoType>, 'name' | 'packages' | 'version'>;
Expand All @@ -18,13 +26,25 @@ export const defaultSdkInfo: DefaultSdkInfo = {
],
version: SDK_VERSION,
};
let DefaultPii: boolean | undefined = undefined;

/** Default SdkInfo instrumentation */
export const sdkInfoIntegration = (): Integration => {
const fetchNativeSdkInfo = createCachedFetchNativeSdkInfo();

return {
name: INTEGRATION_NAME,
setup(client) {
const options = client.getOptions();
DefaultPii = options.sendDefaultPii;
if (DefaultPii) {
client.on('beforeSendEvent', (event => {
if (event.user?.ip_address === '{{auto}}') {
delete event.user.ip_address;
}
}));
}
},
setupOnce: () => {
// noop
},
Expand All @@ -36,15 +56,24 @@ async function processEvent(event: Event, fetchNativeSdkInfo: () => Promise<Pack
const nativeSdkPackage = await fetchNativeSdkInfo();

event.platform = event.platform || 'javascript';
event.sdk = event.sdk || {};
event.sdk.name = event.sdk.name || defaultSdkInfo.name;
event.sdk.version = event.sdk.version || defaultSdkInfo.version;
event.sdk.packages = [
const sdk = (event.sdk || {}) as IpPatchedSdkInfo;
sdk.name = sdk.name || defaultSdkInfo.name;
sdk.version = sdk.version || defaultSdkInfo.version;
sdk.packages = [
// default packages are added by baseclient and should not be added here
...(event.sdk.packages || []),
...(sdk.packages || []),
...((nativeSdkPackage && [nativeSdkPackage]) || []),
];

// Patch missing infer_ip.
sdk.settings = {
infer_ip: DefaultPii ? 'auto' : 'never',
// purposefully allowing already passed settings to override the default
...sdk.settings
};

event.sdk = sdk;

return event;
}

Expand Down
84 changes: 82 additions & 2 deletions packages/core/test/integrations/sdkinfo.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Event, EventHint, Package } from '@sentry/core';
import type { Client, Event, EventHint, Package } from '@sentry/core';
import { SDK_NAME, SDK_VERSION } from '../../src/js';
import { sdkInfoIntegration } from '../../src/js/integrations/sdkinfo';
import { NATIVE } from '../../src/js/wrapper';
Expand Down Expand Up @@ -85,9 +85,89 @@ describe('Sdk Info', () => {
expect(processedEvent?.sdk?.name).toEqual(SDK_NAME);
expect(processedEvent?.sdk?.version).toEqual(SDK_VERSION);
});

it('Add none setting when defaultIp is undefined', async () => {
mockedFetchNativeSdkInfo = jest.fn().mockResolvedValue(null);
const mockEvent: Event = {};
const processedEvent = await processEvent(mockEvent, {}, undefined);

expect(processedEvent?.sdk?.name).toEqual(SDK_NAME);
expect(processedEvent?.sdk?.version).toEqual(SDK_VERSION);
// @ts-expect-error injected type.
expect(processedEvent?.sdk?.settings?.infer_ip).toEqual('never');
});

it('Add none setting when defaultIp is false', async () => {
mockedFetchNativeSdkInfo = jest.fn().mockResolvedValue(null);
const mockEvent: Event = {};
const processedEvent = await processEvent(mockEvent, {}, false);

expect(processedEvent?.sdk?.name).toEqual(SDK_NAME);
expect(processedEvent?.sdk?.version).toEqual(SDK_VERSION);
// @ts-expect-error injected type.
expect(processedEvent?.sdk?.settings?.infer_ip).toEqual('never');
});

it('Add auto setting when defaultIp is true', async () => {
mockedFetchNativeSdkInfo = jest.fn().mockResolvedValue(null);
const mockEvent: Event = {};
const processedEvent = await processEvent(mockEvent, {}, true);

expect(processedEvent?.sdk?.name).toEqual(SDK_NAME);
expect(processedEvent?.sdk?.version).toEqual(SDK_VERSION);
// @ts-expect-error injected type.
expect(processedEvent?.sdk?.settings?.infer_ip).toEqual('auto');
});

it('removes ip_address if it is "{{auto}}"', () => {
const mockHandler = jest.fn();

const client = {
getOptions: () => ({ sendDefaultPii: true }),
on: (eventName: string, cb: (event: any) => void) => {
if (eventName === 'beforeSendEvent') {
mockHandler.mockImplementation(cb);
}
}
};

sdkInfoIntegration().setup!(client as any);

const testEvent = { user: { ip_address: '{{auto}}' } };
mockHandler(testEvent);

expect(testEvent.user.ip_address).toBeUndefined();
});

it('keeps ip_address if it is not "{{auto}}"', () => {
const mockHandler = jest.fn();

const client = {
getOptions: () => ({ sendDefaultPii: true }),
on: (eventName: string, cb: (event: any) => void) => {
if (eventName === 'beforeSendEvent') {
mockHandler.mockImplementation(cb);
}
}
};

sdkInfoIntegration().setup!(client as any);

const testEvent = { user: { ip_address: '1.2.3.4' } };
mockHandler(testEvent);

expect(testEvent.user.ip_address).toBe('1.2.3.4');
});
});

function processEvent(mockedEvent: Event, mockedHint: EventHint = {}): Event | null | PromiseLike<Event | null> {
function processEvent(mockedEvent: Event, mockedHint: EventHint = {}, sendDefaultPii?: boolean): Event | null | PromiseLike<Event | null> {
const integration = sdkInfoIntegration();
if (sendDefaultPii != null) {
const mockClient: jest.Mocked<Client> = {
getOptions: jest.fn().mockReturnValue({ sendDefaultPii: sendDefaultPii }),
on: jest.fn(),
} as any;
integration.setup!(mockClient);
}
return integration.processEvent!(mockedEvent, mockedHint, {} as any);
}