diff --git a/.changeset/strict-identify-validation.md b/.changeset/strict-identify-validation.md new file mode 100644 index 0000000000..162a571bfb --- /dev/null +++ b/.changeset/strict-identify-validation.md @@ -0,0 +1,5 @@ +--- +'posthog-js': minor +--- + +Reject the strings "undefined" and "null" in posthog.identify(). All invalid distinct IDs now log a critical console error (always visible, not debug-only). diff --git a/packages/browser/src/__tests__/identify.test.ts b/packages/browser/src/__tests__/identify.test.ts index cccf809f99..406f4b0b79 100644 --- a/packages/browser/src/__tests__/identify.test.ts +++ b/packages/browser/src/__tests__/identify.test.ts @@ -41,6 +41,35 @@ describe('identify', () => { ) }) + describe('invalid distinct_id', () => { + it.each([ + ['undefined', undefined, 'Unique user id has not been set in posthog.identify'], + ['null', null, 'Unique user id has not been set in posthog.identify'], + ['empty string', '', 'Unique user id has not been set in posthog.identify'], + ['whitespace only', ' ', 'Unique user id has not been set in posthog.identify'], + ['false', false, 'Unique user id has not been set in posthog.identify'], + [ + 'the string "undefined"', + 'undefined', + 'The string "undefined" was set in posthog.identify which indicates an error. This ID should be unique to the user and not a hardcoded string.', + ], + [ + 'the string "null"', + 'null', + 'The string "null" was set in posthog.identify which indicates an error. This ID should be unique to the user and not a hardcoded string.', + ], + ])('should reject %s and log a critical error', async (_label, invalidId, expectedMessage) => { + const token = uuidv7() + const beforeSendMock = jest.fn().mockImplementation((e) => e) + const posthog = await createPosthogInstance(token, { before_send: beforeSendMock }) + + posthog.identify(invalidId as any) + + expect(beforeSendMock).not.toHaveBeenCalled() + expect(mockLogger.critical).toHaveBeenCalledWith(expectedMessage) + }) + }) + it('should send $is_identified = true with the identify event and following events', async () => { // arrange const token = uuidv7() diff --git a/packages/browser/src/posthog-core.ts b/packages/browser/src/posthog-core.ts index f08fc72212..177fda4bb8 100644 --- a/packages/browser/src/posthog-core.ts +++ b/packages/browser/src/posthog-core.ts @@ -2290,6 +2290,26 @@ export class PostHog implements PostHogInterface { ) } + private _validateIdentifyId(id: string | undefined): id is string { + if (!id || isEmptyString(id)) { + logger.critical('Unique user id has not been set in posthog.identify') + return false + } + if (id === COOKIELESS_SENTINEL_VALUE) { + logger.critical( + `The string "${id}" was set in posthog.identify which indicates an error. This ID is only used as a sentinel value.` + ) + return false + } + if (isDistinctIdStringLike(id) || ['undefined', 'null'].includes(id.toLowerCase())) { + logger.critical( + `The string "${id}" was set in posthog.identify which indicates an error. This ID should be unique to the user and not a hardcoded string.` + ) + return false + } + return true + } + /** * Associates a user with a unique identifier instead of an auto-generated ID. * Learn more about [identifying users](/docs/product-analytics/identify) @@ -2342,22 +2362,7 @@ export class PostHog implements PostHogInterface { ) } - //if the new_distinct_id has not been set ignore the identify event - if (!new_distinct_id) { - logger.error('Unique user id has not been set in posthog.identify') - return - } - - if (isDistinctIdStringLike(new_distinct_id)) { - logger.critical( - `The string "${new_distinct_id}" was set in posthog.identify which indicates an error. This ID should be unique to the user and not a hardcoded string.` - ) - return - } - if (new_distinct_id === COOKIELESS_SENTINEL_VALUE) { - logger.critical( - `The string "${COOKIELESS_SENTINEL_VALUE}" was set in posthog.identify which indicates an error. This ID is only used as a sentinel value.` - ) + if (!this._validateIdentifyId(new_distinct_id)) { return } diff --git a/packages/browser/terser-mangled-names.json b/packages/browser/terser-mangled-names.json index 2c1debe16e..d3fe571fc8 100644 --- a/packages/browser/terser-mangled-names.json +++ b/packages/browser/terser-mangled-names.json @@ -562,6 +562,7 @@ "_urlTriggerStatus", "_urlTriggers", "_validateEmail", + "_validateIdentifyId", "_validateSampleRate", "_visibilityChangeListener", "_visibilityStateListener",