Skip to content

Fix iOS crash: sanitize forwarded event params before JSON serialization#3

Merged
ianrumac merged 1 commit into
superwall:mainfrom
bilck:fix/sanitize-forwarded-event-params
Jul 21, 2026
Merged

Fix iOS crash: sanitize forwarded event params before JSON serialization#3
ianrumac merged 1 commit into
superwall:mainfrom
bilck:fix/sanitize-forwarded-event-params

Conversation

@bilck

@bilck bilck commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

toJsonString in the iOS bridge can crash the app with an uncaught Objective-C NSException when a forwarded event carries a non-JSON-serializable param value.

Crash

*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
    reason: 'Invalid type in JSON write (__SwiftValue)'
  sendToUnity(method:data:)
  SuperwallDelegate.handleSuperwallEvent(withInfo:)
  SuperwallKit Superwall.track
  DeviceHelper.getEnrichment
  IdentityManager.identify(userId:options:)

Root cause

serializeEventInfo copies the native SuperwallEventInfo.params verbatim into the payload that sendToUnity passes to JSONSerialization.data(withJSONObject:). When a param value is not a JSON-native type (a Swift enum/struct/URL bridged as __SwiftValue), JSONSerialization raises an Objective-C NSInvalidArgumentException. That is an NSException, not a Swift Error, so the try? in toJsonString does not catch it and the app hard-crashes.

Reproduction

Deterministic on iOS at launch: after Superwall.Identify(...), the SDK runs getEnrichment and tracks an event whose params carry such a value; the delegate forwards it via handleSuperwallEventsendToUnity and the app terminates. Verified on iPad, iOS 26.5, Unity IL2CPP, SDK 0.2.4.

Fix

Route toJsonString through a recursive sanitizer that coerces any non-JSON leaf to its String(describing:) form, and guard with isValidJSONObject so residual edge cases (e.g. non-finite Doubles) drop the message instead of crashing. This protects the entire native→Unity path, not just event forwarding, and is a no-op for already-valid payloads.

Notes

  • There is currently no SuperwallOptions flag to disable/filter event forwarding, and dropping the delegate is not viable for integrators who rely on it for entitlements — so guarding the bridge is the appropriate layer.
  • Alternative considered: wrapping JSONSerialization in an Objective-C @try/@catch. Sanitizing up front was chosen because it preserves the payload (as strings) instead of dropping the whole message, and keeps everything in Swift.

serializeEventInfo copies the native SuperwallEventInfo.params verbatim into the
payload sendToUnity feeds to JSONSerialization.data(withJSONObject:). When params
contain a value that is not a JSON-native type (a Swift enum/struct/URL bridged
as __SwiftValue), JSONSerialization raises an Objective-C
NSInvalidArgumentException ("Invalid type in JSON write (__SwiftValue)"). That is
an NSException, not a Swift Error, so the `try?` in toJsonString cannot catch it
and the app hard-crashes.

Reproduces reliably on iOS at launch: after Superwall.Identify the SDK runs
getEnrichment and tracks an event whose params carry such a value; the delegate
forwards it via handleSuperwallEvent -> sendToUnity and the app terminates.

Route toJsonString through a recursive sanitizer that coerces any non-JSON leaf
to its String(describing:) form, and guard with isValidJSONObject so residual
edge cases (e.g. non-finite Doubles) drop the message instead of crashing. This
protects the entire native->Unity path, not just event forwarding.
@ianrumac
ianrumac merged commit 11dc153 into superwall:main Jul 21, 2026
2 checks passed
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.

2 participants