Your iOS build archived without warnings, Transporter showed a green check, then an email from App Store Connect landed with ITMS-90338 and a list of selector names that look more like internal Apple symbols than anything in your project. The question is whether this is something you wrote, something an npm or CocoaPods dependency pulled in, and what the smallest set of changes is that lets the next upload process cleanly.
Short answer
ITMS-90338 means App Store Connect's static scan found Objective-C selectors in your IPA whose names match Apple's list of private APIs. The references are almost always inside a compiled third-party framework that was linked through npm, CocoaPods, Swift Package Manager, or a vendored xcframework. The fix path is to identify the responsible binary with nm or otool, bump or replace the dependency, and only rename your own methods when one of your own selectors actually collides with a private Apple name. The same diagnostic wording is documented across multiple Apple Developer Forums threads on ITMS-90338.
What you should know
- ITMS-90338 is a binary scan, not a source scan. App Store Connect reads selector strings from a section of the compiled Mach-O. A project that builds cleanly in Xcode can still fail when a linked framework references a private selector.
- Underscore-prefixed names are not automatically forbidden. Only names on Apple's internal private API list trigger the rejection. The email always quotes the exact selectors so you can search for them directly.
- The most common source is a stale npm package, not first-party code. React Native, Expo, and Capacitor apps drag in dozens of compiled frameworks through CocoaPods or Swift Package Manager. One of those frameworks is the usual cause.
- The diagnostic email is honest about static libraries. The wording says some of these references may be located in a static library that was included with your app, and that they must be removed.
- A rejection blocks both App Store submission and TestFlight. The scan runs at ingestion, before either route. There is no path that lets a flagged binary through to internal testers.
- A new build number is required after any fix. App Store Connect deduplicates by version plus build, so reusing the same CFBundleVersion can hide the corrected binary behind the rejected one.
Why does App Store Connect flag ITMS-90338?
The short answer is that Apple keeps an internal list of selector names that belong to private classes and private methods inside system frameworks, and the App Store Connect ingestion pipeline refuses to process any IPA that links a selector with one of those names. The selector strings are easy for Apple to find. Every Objective-C selector your binary references sits in a __TEXT,__objc_methname section of the Mach-O as a null-terminated ASCII string, and the static linker keeps each one even when the call path is unreachable from your Swift entry point.
The flagged selectors fall into two groups. The first group is underscore-prefixed names like _isKeyDown, _modifiedInput, _modifierFlags, and _setSpellCheckingEnabled:, which are obviously private. The second group is innocuous-looking names like applicationWillTerminate, setOrientation:animated:, or webSocket:didReceiveMessage:, which collide with private signatures on Apple classes even though similar names exist elsewhere on public classes. The flag is name-based, not class-based, so the scan does not need to understand which receiver the selector targets.
Cases reported by developers in the Apple Developer Forums thread on AVFCore and ITMS-90338 show the same SDKs repeating across different apps. The pattern points to a small set of third-party frameworks rather than to original code from the app developer.
How do I find which framework references the private selectors?
Three tools cover the diagnostic work. None of them require source access to the offending framework.
The first is nm. After archiving, open the .ipa, navigate to Payload/<App>.app/Frameworks/, and run nm -u on each framework binary to list referenced symbols. Pipe the output through grep for the selector quoted in the rejection email. A direct hit pins the framework.
The second is strings together with grep. Selector names live as plain ASCII in the binary, so strings <Framework>.framework/<Framework> | grep _isKeyDown returns a non-empty result on the framework that introduced the reference. This works even when the symbol is bound dynamically and nm shows nothing useful.
The third is otool -ov. Running otool -ov on a framework binary prints the Objective-C metadata, including the full method list. When the rejection lists a selector that does not appear in the email's framework hint, otool over each linked binary is the most exhaustive option.
Once the responsible framework is identified, the rest of the work follows from the build system that brought it in. CocoaPods entries live in the Podfile.lock, Swift Package Manager entries in Package.resolved, and npm-managed iOS code is usually a transitive dependency of a React Native or Expo module declared in package.json.
What are the common npm packages behind this rejection?
The pattern across issue 33789 on the React Native repository, issue 18686 on the Expo repository, and issue 4338 on the firebase-ios-sdk repository is a small group of frequent offenders.
In the React Native and Expo space, older versions of react-native-webview, react-native-onesignal, react-native-keyboard-input, react-native-webrtc, and the Capacitor WebView bridge have all surfaced flagged selectors. Most maintainers shipped releases between 2022 and 2024 that scrubbed the offending references. In the native streaming and video category, the AgoraRtcEngineKit, JitsiMeetSDK, libksygpulive, and AmazonIVSPlayer frameworks have all appeared in rejection emails for apps that pinned older versions.
Bumping the package to its current release usually clears the rejection without any plist or Swift change on the developer side. When a vendor has not patched the framework, the practical options narrow to replacing the dependency or removing the feature it provides.
How do I fix it without renaming my own code?
Renaming a Swift method is the last step, not the first. The right sequence is dependency-first.
Step one is to read the email carefully. The diagnostic always lists a framework hint in brackets after each selector. That hint is the name of the dynamically-loaded private Apple framework whose selector was matched, not the third-party binary that referenced it. The information that matters is the selector list, because the selector points at the third-party binary on your side of the link.
Step two is to grep for each selector inside node_modules/, Pods/, and SourcePackages/. A literal match on the selector string in a vendor binary or source file confirms the source. When the project uses precompiled xcframeworks, the grep needs to run against the compiled binary inside the xcframework, not against any header.
Step three is to update the dependency. Most maintainers publish a release note that names ITMS-90338 explicitly. Bumping to that release and rebuilding clears the rejection in the majority of cases.
Step four is to rename your own code only when none of the previous steps located the reference in a dependency. This is rare. A custom method called _setSpellCheckingEnabled: or applicationWillTerminate on a custom class can trigger the scan, but app developers almost never write selectors with those exact shapes.
Step five is to remove the dependency when the maintainer has not patched it. Apple's diagnostic wording is explicit that the references must be removed when they live inside a static library that cannot be updated.
Which selectors and frameworks appear most often?
The table below collects the selector and SDK pairs that recur across public threads on ITMS-90338. The list is illustrative, not exhaustive; the email you receive carries the authoritative selector list for your build.
| Selector or symbol | Likely source | Resolution |
|---|---|---|
_isKeyDown, _modifiedInput, _modifierFlags | older React Native core, react-native-keyboard-input | Bump react-native and the keyboard package to a current release |
applicationNameForUserAgent, setNavigationDelegate: | older Capacitor WebView bridge | Update Capacitor to the patched release |
_CMTimebaseCreateWithMasterClock | libksygpulive, AmazonIVSPlayer, related video SDKs | Replace with a newer vendor build or switch SDK |
_setBlurRadius, _setSpellCheckingEnabled: | custom UIKit visual or text customisation | Replace with a public API or rename the method |
setUid:, stopPreview, transform: | older AgoraRtcEngineKit | Update to the patched Agora release |
What to watch out for
- Treating the framework name in the email as the culprit. The bracketed framework is the private Apple framework the selector belongs to, not the third-party binary referencing it. Use
nmandstringsto find the actual source. - Reusing the same CFBundleVersion after a fix. App Store Connect deduplicates by version plus build, and a recycled build number can quietly hide a corrected binary behind the rejected upload.
- Assuming
npm auditornpm outdatedcatches this. Those tools surface npm registry metadata; they do not inspect the precompiled iOS binaries that ship inside React Native or Expo modules. - Renaming first and asking later. A rename that hides a private selector in your own code still leaves the same selector in the linked dependency. The scan flags the dependency reference regardless of what your Swift looks like.
- Confusing ITMS-90338 with the privacy manifest rejections. ITMS-91053 and the NSPrivacyAccessedAPI family cover required reason API declarations, not private API references. The fixes do not overlap, and a clean privacy manifest does nothing to clear ITMS-90338.
- Mistaking the scan for a substitute for a security review. ITMS-90338 reports a single class of policy violation. Hardcoded secrets, leaky deep links, weak App Transport Security, and unencrypted storage all sit outside it. Those checks live in a separate pass against OWASP MASVS. For developers who want an external read of the compiled IPA before submission, PTKD.com (https://ptkd.com) is one platform focused specifically on pre-submission scanning of iOS and Android binaries against OWASP MASVS controls.
Key takeaways
- ITMS-90338 is a name-collision check. The scan finds Objective-C selector strings inside your Mach-O that match Apple's private API list, not a behavioural problem in your code.
- The reference almost always sits in a third-party framework that came in through CocoaPods, Swift Package Manager, or a React Native or Expo module. Look there before touching your Swift.
- Identify the responsible binary with
nm,strings, orotool -ovagainst the frameworks inside the .ipa. The exact selector quoted in the email is the search key. - Bump or replace the offending dependency, then increment the build number and re-upload. A correct fix usually requires no source-level change in your app target.
- For an independent read of the compiled IPA against OWASP MASVS before submission, scanning platforms like PTKD.com (https://ptkd.com) run that pass and flag private API references alongside secret storage and network configuration issues, so the same upload does not get rejected twice for unrelated reasons.



