Skip to content

fix: [SDK-5426] reject blank identity strings - #1760

Merged
abdulraqeeb33 merged 5 commits into
mainfrom
ar/sdk-5426
Oct 6, 2026
Merged

abdulraqeeb33 merged 5 commits into
mainfrom
ar/sdk-5426

Conversation

@abdulraqeeb33

@abdulraqeeb33 abdulraqeeb33 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

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

OSInputGuard rejects 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 / removeAliases
  • addTag / addTags (key only). An empty tag value is allowed, including one that contains a null byte. These methods take [String: String], so an ObjC NSNull or NSNumber crashes in the Swift bridge before the guard. That crash is pre-existing.
  • removeTag / removeTags
  • addEmail / removeEmail and addSms / removeSms (empty only, not format checks)
  • addTrigger / addTriggers (key only). An empty trigger value is allowed. A null trigger value is rejected. addTriggers also rejects NSNull.
  • removeTrigger / removeTriggers
  • trackEvent(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 isMissing runs. 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

OSInputGuardTests covers nil, empty, whitespace, a null byte, and blank map or list entries. InputGuardUserTests resets the shared user and checks aliases, tags, email, SMS, login, and trackEvent(""). 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

  • Notifications
    • Display
    • Open
    • Push Processing
    • Confirm Deliveries
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes

Checklist

Overview

  • I have filled out all REQUIRED sections above
  • PR does one thing
  • Any Public API changes are explained in the PR details and conform to existing APIs

Testing

  • I have included test coverage for these changes, or explained why they are not needed
  • All automated tests pass, or I explained why that is not possible
  • I have personally tested this on my device, or explained why that is not possible

Final pass

  • Code is as readable as possible.
  • I have reviewed this PR myself, ensuring it meets each checklist item

AR Abdul Azeez and others added 2 commits October 5, 2026 11:46
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>
@abdulraqeeb33
abdulraqeeb33 requested a review from a team October 5, 2026 18:05

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Open in Web View Automation 

Sent by Cursor Automation: PR Reviews

Comment thread iOS_SDK/OneSignalSDK/Source/OneSignal.m

@nan-li nan-li left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread iOS_SDK/OneSignalSDK/OneSignalUserTests/InputGuardUserTests.swift
@abdulraqeeb33

Copy link
Copy Markdown
Contributor Author

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>
@nan-li

nan-li commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

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

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>
@abdulraqeeb33

Copy link
Copy Markdown
Contributor Author

Added in 7bb6d3d. isMissing rejects a null byte, including login. An empty tag or trigger value can still contain one, matching Android allowEmptyValue.

@fadi-george

Copy link
Copy Markdown
Collaborator
  • Non-blocking: OneSignalUserManagerImpl.swift addTags/addAliases/removeTags/removeAliases, ObjC callers passing NSNumber or NSNull values crash in the Swift bridge before OSInputGuard runs ("Could not cast value of type 'NSNull' to 'NSString'"). Pre-existing on main. The NSNull branch in hasMissingEntries only applies to triggers, so the "null tag value is rejected" line in the description doesn't hold for addTags.

@abdulraqeeb33

Copy link
Copy Markdown
Contributor Author

Agreed. addTags takes [String: String], so NSNull and NSNumber crash in the bridge before the guard. That is pre-existing. The description now says the null-value reject is the trigger path only. An empty tag value, including one with a null byte, stays allowed.

@abdulraqeeb33
abdulraqeeb33 requested review from a team and nan-li October 6, 2026 17:33
Comment thread iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSInputGuard.swift
Co-authored-by: Cursor <cursoragent@cursor.com>
@abdulraqeeb33
abdulraqeeb33 requested a review from nan-li October 6, 2026 18:09
@abdulraqeeb33
abdulraqeeb33 merged commit a040da8 into main Oct 6, 2026
4 checks passed
@abdulraqeeb33
abdulraqeeb33 deleted the ar/sdk-5426 branch October 6, 2026 19:26
nan-li pushed a commit that referenced this pull request Oct 7, 2026
Co-authored-by: AR Abdul Azeez <abdul@onesignal.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit a040da8)
@github-actions github-actions Bot mentioned this pull request Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants