App Review rejections that cite App Transport Security usually arrive with a short paragraph from Apple explaining that the build sets NSAllowsArbitraryLoads to true without sufficient justification. In a React Native project, that flag almost always slipped in during the template bootstrap, not because the app actually needs unencrypted traffic in production. The fix is narrow rather than global, and the verification steps are straightforward.
Short answer
Apple's App Transport Security blocks insecure network connections by default since iOS 9, per Apple's NSAppTransportSecurity reference. React Native's default iOS template enables NSAllowsArbitraryLoads so the Metro bundler can serve JavaScript from localhost during development. The fix for an App Review rejection is to set NSAllowsArbitraryLoads to false in the release Info.plist, remove the broad localhost exception, and add NSExceptionDomains entries only for legitimate non-HTTPS endpoints with a written justification in App Store Connect.
What you should know
- App Transport Security is on by default for every iOS app. Apple enforces HTTPS with TLS 1.2 minimum unless the Info.plist declares an exception.
- NSAllowsArbitraryLoads set to true is the most common trigger for an ATS rejection. Apple's automated screen reads the Info.plist before any human review and flags the global opt-out.
- React Native's iOS template adds an ATS exception that enables localhost loading. Fine in development, a rejection vector in production.
- pod install can rewrite the NSAppTransportSecurity key. A long-running React Native bug, facebook/react-native#41874, resets the value on some toolchain versions.
- NSExceptionDomains is the safe path. Each entry allows a specific host without disabling ATS for the rest of the network stack.
- App Review reads the compiled binary, not the source repository. Whatever ends up in the IPA Info.plist is what gets evaluated.
Why does App Review reject a React Native build for App Transport Security?
The short answer is that Apple's automated screen reads the compiled Info.plist before a human reviewer opens the app. According to Apple's NSAllowsArbitraryLoads documentation, the key is a boolean that disables ATS for every network call in the binary. When that boolean is set to true in a production build, Apple expects a written justification inside App Store Connect. Without it, the reviewer can return the build under the security expectations spelled out across the App Review Guidelines.
In a React Native project, the chain of events is almost always the same. The react-native init template, and npx create-expo-app in many versions, drop a default Info.plist with an NSAppTransportSecurity dictionary configured so the Metro bundler can serve JavaScript over HTTP from localhost. Developers ship the build without revisiting that section. When Apple sees NSAllowsArbitraryLoads: true in the final IPA, the screen flags it.
The rejection language varies. Common patterns include 'Your app uses the NSAllowsArbitraryLoads key in Info.plist,' 'We are unable to verify that your use of NSAllowsArbitraryLoads is appropriate,' or a direct request for justification with a deadline. Each version means the same thing in practice: remove the global flag, or explain why the app truly cannot function without it.
Where does NSAllowsArbitraryLoads come from in a React Native project?
Three places put it there.
The first is the iOS template scaffolded by the React Native CLI. The default ios/AppName/Info.plist contains an NSAppTransportSecurity dictionary set up so the Metro bundler can serve JavaScript over HTTP from localhost. The boilerplate value varies by version, but the localhost exception is always present.
The second is community plugins. Some older React Native libraries, particularly older video and WebView packages, recommend setting NSAllowsArbitraryLoads to true so the library can load content from arbitrary hosts. The recommendation reflects developer convenience rather than what Apple's reviewer expects to see in the binary.
The third is CocoaPods regeneration. A long-running issue, facebook/react-native#41874, reports that on some toolchain versions, running pod install rewrites the NSAppTransportSecurity dictionary in Info.plist with a template default that re-enables NSAllowsArbitraryLoads. The fix is committed upstream, but projects pinned to older React Native or older Expo can still hit this regression after every pod install.
The diagnostic is simple: archive the build in Xcode, export the IPA, rename to .zip, expand it, and open Payload/YourApp.app/Info.plist. Whatever you read at that path is what Apple's automated review sees.
How do I configure NSExceptionDomains for a specific HTTP endpoint?
NSExceptionDomains is the targeted alternative to NSAllowsArbitraryLoads. Each entry in the dictionary is keyed by hostname, with subkeys that control TLS behavior for that specific host.
The useful subkeys for a single HTTP-only host are NSExceptionAllowsInsecureHTTPLoads set to true (allowing plain HTTP), NSIncludesSubdomains as a boolean for whether subdomains inherit the exception, and NSExceptionMinimumTLSVersion when the host supports TLS but not at the default 1.2 floor. The same entry can also include NSExceptionRequiresForwardSecrecy set to false when the certificate chain does not support Perfect Forward Secrecy.
Add the entries inside the NSAppTransportSecurity dictionary, alongside an explicit NSAllowsArbitraryLoads: false. Apple's screen reads the global flag first, so setting it to false is part of the fix even when exception domains are also configured. The narrower the scope, the smoother the resubmission.
What does a clean production Info.plist look like?
Compare common configurations against each other.
| Configuration | NSAllowsArbitraryLoads | NSExceptionDomains | Outcome at App Review |
|---|---|---|---|
React Native default after init | true | localhost only | Flagged for global opt-out |
| HTTPS-only production app | false | empty or absent | No ATS scrutiny |
| Mixed HTTPS plus one legacy HTTP host | false | hostname with NSExceptionAllowsInsecureHTTPLoads true | Passes when justification is sound |
| Streaming from a CDN with older TLS | false | hostname with NSExceptionMinimumTLSVersion TLSv1.1 | Passes for narrow scope |
| Global opt-out with rationale in review notes | true | not used | Conditional, depends on reviewer |
The pattern Apple wants is the smallest exception that lets the app work. A single hostname with a clear reason in the App Review notes is treated as cooperative. A global flag without context is treated as the developer not engaging with ATS at all.
How do I verify my ATS configuration before resubmitting?
Three checks catch most regressions.
The first check is the compiled Info.plist inside the IPA. Build for archive, export the IPA from Xcode Organizer, rename to .zip, expand, and open Payload/YourApp.app/Info.plist. Search for NSAppTransportSecurity. If you read NSAllowsArbitraryLoads true at this stage, the production binary is still misconfigured regardless of what the source repository says.
The second check is the Privacy Manifest. Apple's screen reads the PrivacyInfo.xcprivacy file alongside Info.plist, so a misconfigured ATS section combined with missing Privacy Manifest entries can compound the rejection language and slow the resubmission.
The third check is the network test set from the OWASP Mobile Application Security project. The OWASP MASVS network controls expect that all network communications are encrypted in transit. A clean ATS configuration is the iOS implementation of MASVS-NETWORK-1. A structured pre-submission scan against the compiled IPA catches both ATS flags and the related certificate pinning gaps that often travel with them.
For builders who want an external automated read of a compiled IPA before the next App Review attempt, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-submission OWASP MASVS scanning, including ATS configuration checks against the final binary rather than the source repo.
What to watch out for
Three patterns cause repeat rejections after a first fix.
The first is the pod install regression. If the release script runs pod install as part of the build, verify the NSAppTransportSecurity dictionary still says NSAllowsArbitraryLoads false in the resulting Info.plist. The bug tracked in facebook/react-native#41874 has been patched in recent versions, but a stale Podfile.lock or an older React Native pin can still trigger it on the next run.
The second is the App Review notes field. When the app does need a real exception, write a short, factual justification in App Store Connect: which host, why HTTPS is not available, and what data the connection carries. Apple's reviewers read the notes and accept clear, scoped rationales. Silence is interpreted as no justification.
The third is the split between debug and release builds. Some teams keep a debug Info.plist with NSAllowsArbitraryLoads true so Metro reloads work, and a release Info.plist with ATS enforced. That split is valid, but a misconfigured release scheme can ship the debug Info.plist by accident. The cleaner pattern is one Info.plist with a narrow localhost exception that does not extend to the public internet.
Key takeaways
- Set NSAllowsArbitraryLoads to false in the release Info.plist. A clean global flag is the first thing the automated screen checks.
- Use NSExceptionDomains for any genuine non-HTTPS host. The narrower the scope, the smoother the review.
- Inspect the compiled IPA, not the source repo. App Review reads the binary, and pod install regressions can desync the two.
- Write a short justification in App Store Connect when an exception is real. A factual two-sentence reason resolves most reviewer follow-ups.
- Some teams outsource the pre-submission scan. Platforms like PTKD.com run automated OWASP MASVS checks on the compiled IPA, which catches both ATS flags and the certificate pinning gaps that travel with them.




