diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bb59bc358..cdd58117b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. +## Unreleased + +### Important Changes + +- **fix(browser): Ensure IP address is only inferred by Relay if `sendDefaultPii` is `true`** ([#5092](https://github.com/getsentry/sentry-react-native/pull/5092)) + +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! + ## 6.20.0 ### Features diff --git a/packages/core/src/js/integrations/sdkinfo.ts b/packages/core/src/js/integrations/sdkinfo.ts index b90614d5c3..6c4735379c 100644 --- a/packages/core/src/js/integrations/sdkinfo.ts +++ b/packages/core/src/js/integrations/sdkinfo.ts @@ -5,6 +5,13 @@ 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, 'name' | 'packages' | 'version'>; @@ -19,6 +26,7 @@ export const defaultSdkInfo: DefaultSdkInfo = { ], version: SDK_VERSION, }; +let DefaultPii: boolean | undefined = undefined; /** Default SdkInfo instrumentation */ export const sdkInfoIntegration = (): Integration => { @@ -26,6 +34,17 @@ export const sdkInfoIntegration = (): Integration => { 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 }, @@ -37,15 +56,24 @@ async function processEvent(event: Event, fetchNativeSdkInfo: () => Promise { 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 { +function processEvent( + mockedEvent: Event, + mockedHint: EventHint = {}, + sendDefaultPii?: boolean, +): Event | null | PromiseLike { const integration = sdkInfoIntegration(); + if (sendDefaultPii != null) { + const mockClient: jest.Mocked = { + getOptions: jest.fn().mockReturnValue({ sendDefaultPii: sendDefaultPii }), + on: jest.fn(), + } as any; + integration.setup!(mockClient); + } return integration.processEvent!(mockedEvent, mockedHint, {} as any); }