Repository navigation
fix: [SDK-5426] reject blank identity strings - #1760
Conversation
Empty strings were stored as aliases, tags, triggers, and app ids. Reject them before storage so iOS matches the other client SDKs. Co-authored-by: Cursor <cursoragent@cursor.com>
The guard result was discarded, so a nil value still built the trigger dictionary. Tests now reset the shared user and cover the blank alias, tag, event, and trigger paths. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Multi-model review (Claude Sonnet 5.5, GPT 5.6 Sol, Grok 4.7) of SDK-5426. Shared OSInputGuard correctly rejects empty identity strings on the Swift User APIs, and the follow-up keeps a nil trigger value out of the dictionary.
Consider (2/3 Sonnet+Grok): initialize("") / setAppId("") now return before setLaunchOptions: and init, and no longer fall back to storedAppId. The old !newAppId || length == 0 path already refused to persist "" and used the cache. The PR documents the cache skip, but the initialize early return also drops cold-start launch options; a later initialize(realAppId, nil) cannot restore them. No test covers "" vs nil.
Consider (3/3): addTrigger:withValue: only rejects a nil pointer. [NSNull null] is stored, while addTriggers: rejects NSNull. testNilTriggerValueIsNotStored covers only nil.
Consider (2/3 Sonnet+GPT): User addTag / addTags take non-optional String. ObjC nil bridges to "" and is stored. The helper’s NSNull check is not on this path, so the “null tag value is rejected” contract is not enforced at the public ObjC boundary.
Noted: NSNull in a typed [String: String] / [String] can trap in the Swift bridge before the guard (pre-existing). setAppId logs initialize: appId. IAM tests mutate the shared controller.
Sent by Cursor Automation: PR Reviews
nan-li
left a comment
There was a problem hiding this comment.
Should we add NUL byte to have parity with Android? The report of that issue was sampled on Android SDK 5.1.20 but it could come from any platform.
Its added in Android OneSignal/OneSignal-Android-SDK@de6f7d1 |
Assert after each add, and seed a blank subscription before the removes. Co-authored-by: Cursor <cursoragent@cursor.com>
Yes so should we add that to this PR as well? |
A NUL cannot be stored in a text column. isMissing now rejects it, including login. Empty tag and trigger values still may contain one, matching Android. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Added in 7bb6d3d. |
|
|
Agreed. |
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: AR Abdul Azeez <abdul@onesignal.com> Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit a040da8)


Description
One Line Summary
Reject null and empty identity strings through one shared helper.
Details
Motivation
Fixes SDK-5426.
Empty strings were stored as aliases, tags, triggers, and app ids. Null and empty are rejected together on the identity APIs below, matching the other client SDKs under SDK-5327.
Scope
OSInputGuardrejects null and empty for:initialize(appId).""returns before cached app id setup and before remote logging is configured. nil still means the app id has not been provided.addAlias/addAliases(label and id)removeAlias/removeAliasesaddTag/addTags(key only). An empty tag value is allowed, including one that contains a null byte. These methods take[String: String], so an ObjCNSNullorNSNumbercrashes in the Swift bridge before the guard. That crash is pre-existing.removeTag/removeTagsaddEmail/removeEmailandaddSms/removeSms(empty only, not format checks)addTrigger/addTriggers(key only). An empty trigger value is allowed. A null trigger value is rejected.addTriggersalso rejectsNSNull.removeTrigger/removeTriggerstrackEvent(name)login(externalId).""and a null byte are both rejected.Whitespace is still allowed. One blank map or list entry rejects the whole call. A null byte is rejected everywhere
isMissingruns. An empty tag or trigger value may still contain one.setLanguage("")is not rejected. It is the reset to the device language.Testing
Unit testing
OSInputGuardTestscovers nil, empty, whitespace, a null byte, and blank map or list entries.InputGuardUserTestsresets the shared user and checks aliases, tags, email, SMS,login, andtrackEvent(""). In-app message tests cover a blank trigger key and a nil trigger value.Manual testing
Not run. This is a guard before native storage, covered by unit tests.
Affected code checklist
Checklist
Overview
Testing
Final pass