Our App got rejected by App Store on March 17 with the following message from Apple
Mar 17, 2017 at 8:00 PM
From Apple
2. 5 Performance: Software Requirements (iOS)
Thank you for submitting your app.
Upon further review, we found your app out of compliance with the following guideline(s):
Performance - 2.5.2
Your app, extension, and/or linked framework appears to contain code designed explicitly with the capability to change your app’s behavior or functionality after App Review approval, which is not in compliance with App Store Review Guideline 2.5.2 and section 3.3.2 of the Apple Developer Program License Agreement.
This code, combined with a remote resource, can facilitate significant changes to your app’s behavior compared to when it was initially reviewed for the App Store. While you may not be using this functionality currently, it has the potential to load private frameworks, private methods, and enable future feature changes. This includes any code which passes arbitrary parameters to dynamic methods such as dlopen(), dlsym(), respondsToSelector:, performSelector:, method_exchangeImplementations(), and running remote scripts in order to change app behavior and/or call SPI, based on the contents of the downloaded script. Even if the remote resource is not intentionally malicious, it could easily be hijacked via a Man In The Middle (MiTM) attack, which can pose a serious security vulnerability to users of your app.
Next Steps
Perform an in-depth review of your app and remove any code, frameworks, or SDKs that fall in line with the functionality described above and resubmit your app’s binary for review.
Best regards,
App Store Review
List of iOS packages used by us
We were suspecting the CodePush. So, We re-submitted the App without CodePush. Still, Apple rejected by saying the same message.
They have mentioned that We shouldn't use dlopen()
, dlsym()
etc., So, We did the search of whole Repo for those functions. We found those functions in React Native itself. We found dlsym in RCTUtils.m ( https://github.com/facebook/react-native/blob/master/React/Base/RCTUtils.m#L535 ) etc.,
Thanks,
Is there any chance that you accidentally tried to publish a development build instead of a production one? Also, did you remove codepush completely (yarn remove) or you just removed from the main file so maybe it was left on some parts like package.json
and the final bundle?
If not, there's a small possibility RCTUtils.m#L51-L52 is the case indeed.
@brunolemos I can confirm that CodePush is OK ( react-native-mixpanel is safe too)
Please check Crashlytics
and Fabric
, most of the reject cases are caused by those crash analysis tools
@brunolemos are you sure the code you linked could be an issue? I'm not sure if that would be considered "code which passes arbitrary parameters to dynamic methods" since the name of the library to load is hard coded.
@lyahdav no, but it's the code that contains the dlsym
function mentioned by Apple
Apple's analysis tools may see JSC and dlopen()/dlsym() invoked the same binary, leading to a false positive. I recommend commenting out those dlopen() and dlsym() calls so that they are completely gone from the binary and resubmit.
I also want to clarify that React Native's calls to those functions may not have raised that alert. Other libraries may be invoking those methods, too.
@ide if he commented out those dlopen() and dlsym() calls won't it break React Native? Or are you just suggesting that as a process of elimination?
@nihgwu if it's Crashlytics or Fabric why would the many others apps using those libraries be accepted?
I have made some investigations. I searched for the methods mentioned by Apple on all repositories listed in this issue and these are the ones that use them:
PS1: I couldn't check Fabric neither Crashlytics because they aren't open source, I believe.
PS2: This search only catches the direct source code, but some of their dependencies might be using these methods.
So basically react-native
uses ALL methods mentioned by Apple.
The 2nd red flag goes to react-native-onesignal
, which uses 3 of the 5 methods.
PS3: These methods can be used, but as the Apple message said, you can't pass
arbitrary parameters
to them.
Try removing one of these packages and retrying.
I would start with the react-native-table-view
and then react-native-onesignal
.
I hope that helps.
@lyahdav I don't think removing those calls would break React Native in production. Trying to come up with a minimal repro.
All of these methods are quite common in typical iOS programming, especially the latter three. Also historically Apple's automatic scanners sometimes have had false positives (ex: thinking a private API was called when there definitely were no calls to it).
For what it's worth, today the App Store accepted our RN app we submitted a couple days ago. We use RN and Crashlytics (not all of Fabric though).
@ide , and no codePush?
Just wanted to add here that our app also got approved twice this past week. Our app uses, amongst others, code push, bugsnag and onesignal.
It would be good if we could get in touch with someone who knows more about the internals of Apple's review process. For starters, we cannot know for certain that Apple tests _all_ app submissions on _all_ fronts. It's quite possible they limit to a standard subset of checks along with a few more randomly chosen checks. This would speed up the overall review process (remember they sped up the review process? This might be how they do it). That might also explain why not all apps with similar dependencies are being rejected.
Just to contribute, I've been getting approved with CodePush + OneSignal as well.
@sriraman are all your endpoints HTTPS?
Even if the remote resource is not intentionally malicious, it could easily be hijacked via a Man In The Middle (MiTM) attack, which can pose a serious security vulnerability to users of your app
MiTM will usually not be a issue when using HTTPS as Apple in most cases require.
The dlopen
, dlsym
, respondsToSelector:
, performSelector:
, and method_exchangeImplementations
APIs by themselves are public and may be used, the issue is specifically with “code which passes arbitrary parameters to dynamic methods”. E.g. the dlsym
case that @brunolemos linked to refers to functions with static strings, so should be fine.
My suspicion would be CodePush, which is specifically meant to deploy updates _after_ the initial review process. Maybe try resubmitting without that and see if it goes through? The fact that others are able to deploy with it could just be explained by them randomly picking out apps for more granular checks.
FYI There have recently been rejections for people using Rollout (haven’t reread to see if other solutions were affected too). https://news.ycombinator.com/item?id=13817557
@alloy We already submitted the app without CodePush. Still, they rejected the app. :(
@alloy Can you check RCTUtils L535-L538 also.
As far as we know, CodePush (for RN and Cordova, in this context the two are equivalent) itself is well within the Apple guidelines. CodePush and RN do not directly or indirectly pass arbitrary parameters to dynamic methods like dlopen and dlsym. As an app developer you shouldn't drastically change the advertised or intended purpose of your app (don't change your chat messenger into a catalog of obscene content), but from a technical perspective CodePush follows the guidelines. And as @sriraman posted, removing CodePush from a rejected app did not lead to the app's getting accepted -- there are no data points indicating that CodePush is a problem so far.
If you intentionally went out of your way, you could write a RN module or Cordova plugin or a plain Obj-C class for a plain UIKit app (e.g. Rollout, NativeScript) that does invoke those dynamic methods from JavaScript, but neither RN, Cordova, nor UIKit offer or encourage this functionality.
As mentioned above you could enable ATS, which enforces HTTPS for all connections of your app. Using HTTPS would greatly reduce the opportunity for MITM attacks -- an attacker would need to steal your server's private TLS keys or have physical access to the unlocked iOS device.
@sriraman Those calls you linked to also have static arguments. You could try commenting them out anyway to see if your app gets approved, but many people are reporting that their apps with RN and CodePush are getting approved.
I haven't done an appeal in a long time, can you ask them for more details about what exactly is causing the rejection?
We asked for the specific reason. They sent us the following message
Specifically, this was rejected because it contains a library which takes javascript and converts it directly to native code. It would be appropriate to remove all libraries and frameworks which perform such functionality before resubmitting for review.
What should we do next?
which takes javascript and converts it directly to native code
hmm, which library does that?
I have already listed the list of Library we use. No library is doing that. But, Apple App store saying that as the reason. We were suspecting CodePush, Mixpanel, RNTableView, etc., and Resubmitted without using it. Still, They are saying like this.
Can anyone guide us what to do next?
@sriraman so you have already removed react-native-table-view
and the others?
Then I would just ask Apple specifically which lib is the problem.
@sriraman So basically, are you now submitting a plain simple react native app without any dependencies and getting this error ?
@axemclion No. We just removed CodePush, Mixpanel, Fabric and React Native Tableview. We didn't send them plain React Native app.
@brunolemos Yes. We removed React native tableview when we were submitting the app this time.
@sriraman I think you should do the following:
The remote possibility of react native being the rejection cause is very prejudicial to React Native image and community, we must find the real root cause ASAP and end this.
We were able to get an app using CodePush into the app store. I don't believe it would be CodePush causing the problem.
That linked ticket has good excerpts on the rules and why something like CodePush shouldn’t be the problem ^.
I agree with @brunolemos in that you should try to get Apple to answer specific questions through an appeal. Thanks for hanging in there for the rest of us 🙏
Just an aside -- I really doubt this is a factor -- but @sriraman, what version of RN are you using?
@brunolemos Thanks for the suggestion. We spoke with @brentvatne . We are opening our source to him. I think he can help us in finding the issue if we are doing something wrong.
We are starting the Appeal also. I'll let everyone know once we find the root cause for the rejection.
@hilkeheremans We are using older version only (v0.30).
Hey @sriraman so sorry to hear about your app store woes. I have been there many times. Are you using expo/exponent to build your app? As I understand it they still have the ability to hot reload your bundle which as amazingly awesome as I think that is and love that feature I think that is what Apple is cracking down on. I can see both sides of the argument I definitely lean towards the ability to push out fixes and features quickly but can see Apple's point of keeping control of what is in their ecosystem. If you look at their latest review guidelines they removed the verbiage of allowing apps running on the javascriptcore to update remotely. Their response of 'your app takes javascript and converts it to native code' terrifies me a little bit since that is what React Native does at it's core. We have spent the past year moving over our entire platform over to React Native and getting ready to release our first version this week so I am anxiously watching this thread! Good luck! At the end of the day you could have gotten a reviewer who doesn't understand the full scope of how React Native works and is just digging their heels in we have had that happen before on a feature that had been running in 12 other apps we had submitted and gotten approved but this reviewer wouldn't budge so we had to rework that entire feature.
If you look at their latest review guidelines they removed the verbiage of allowing apps running on the javascriptcore to update remotely.
source? from what people have posted above it's still there in the latest agreement.
Their response of 'your app takes javascript and converts it to native code' terrifies me a little bit since that is what React Native does at it's core
React Native doesn't convert JavaScript to native code. It's not much different from what a browser does, provide access to few native APIs from JavaScript.
from what people have posted above it's still there in the latest agreement.
Yup, it’s still allowed. See “Apple Developer Program License Agreement” here.
I was just looking here https://developer.apple.com/app-store/review/guidelines/#performance under the 2.5.2 I thought it used the be in their review guidelines but I guess not. I actually used one of our developer support requests to ask about react native and see if there were any issues using the platform.
@geoc3 Hi, I work Expo -- with Expo specifically we've heard of zero App Store rejections due to this reason. While Apple's policies aren't always clear-cut, we make sure that the Expo codebase follows the Apple guidelines (technically & w.r.t. intent) and since Expo developers generally write only JavaScript, that clears the likelihood of a developer's accidentally breaking the Apple guidelines. The Expo standalone app builder creates the IPA with the right compiler flags so the developer is safe from accidentally submitting a build that contains debug code paths, in the event that those are setting off Apple's analyzer.
All this is to say that between an audited Expo native code base, verified compiler settings, and my own deep knowledge of how RN works and how it relates to Apple's message (n.b. RN does not convert JS to native code nor does it "pass arbitrary parameters to dynamic methods"), both Expo and other bare RN apps (without custom code in violation) comply with the Apple guidelines to the best of my knowledge.
Hi @ide thanks for the detailed response and definitely eases concerns a bit. I just know how Apple reviews can go hence the slight bit of nervousness. Hoping like you said in a previous comment the problem is figured out and resolved and everyone can get on down the road building fantastic apps with Expo and React Native! Such an awesome platform!
I'd suggest the React Native core team to write a small blog post just to make things clear to the community. We know that React Native has nothing to do with these rejections, but people keep raising this concern on public forums. They might just need to listen to it louder and clear, in an official and final way.
@sriraman React Native 0.30 is really old. Too much has changed in the last 8 months, maybe even code related to the methods mentioned by Apple. It's hard to give support for that, you should really upgrade it.
I suspect it's due to react-native-one-signal.
The selector helpers here allow swizzling out any arbitrary methods. If these selector helpers are accessible from JavaScript in any way, then it's very likely that this is causing the issue, since you would be able to swap out native functions pretty easily.
Some more details after diving into how the OneSignal SDK works:
It's swizzling out a lot of selectors on UIApplicationDelegate, including nearly all of the lifecycle methods on it.
There's an initWithLaunchOptions
method on RCTOneSignal that isn't exposed using RCT_EXPORT_METHOD
, but one of the callback options is handled by one of the swizzled selectors.
I can't think of a way just yet using the public React Native APIs or public React Native OneSignal APIs that would allow arbitrary code execution, but if RCT_EXPORT_MODULE
exposes the hidden methods on the module to JSC in some other way, then it could be possible.
It seems pretty likely that Apple's static analyzer was able to catch something there.
I'm probably not going to dive much deeper into this, but I would recommend removing OneSignal first and then submitting again. I'd definitely be interested in their response after that.
The two app reviews we passed last week - one last monday, one just this sunday - both had react-native-onesignal
in them.
I'm not saying react-native-onesignal
is _not_ the problem since we cannot be certain Apple fully analyzes all submissions, but if they do full checks all the time, it is definitely not the issue here.
In short, I would recommend you still follow @clarle's suggestion to resubmit without onesignal -- let us know how it turns out so we can then collectively jump on a PR in react-native-onesignal
:-)
To get back to an earlier question I asked -- the OP is stating that they are still using RN v0.30. Question for the RN connaisseurs: as @brunolemos implies, is there any chance there is some naughty code in there that was removed in later versions?
Hey @sriraman just checking to see if you ever found out what was causing Apple to reject your app. Thanks!
Apple rejected our app again with the same message. They didn't give any proper reason for the rejection after reviewing the app for past 7 days. Now, We are starting the appeal process to App Review Board. I'll update the same thread once we get the response from the App Review Board
I think Apple will eventually reject apps that use CodePush even if they are allowed now. This is because it's just out of compliance with the guidelines to update apps or their parts without App Store. And it's completely not related to system methods that are used or not.
And because RN allows running external js code itself, there is a non-zero probability that one day Apple will reject all apps that use RN.
I think RN should prevent loading & running external code to protect from that scenario. Once Apple rejects RN, it won't be easy to convince them that after last changes it's not possible anymore or it will take ages. RN should be proactive and protect from that now. Not wait for the disaster.
@kapone89 That means web browsers should also be banned it runs external js code? Seems legit. Worst comment I've read in a while.
@ptomasroos You are completely right, but you only think about it from a developer perspective. But look at it from a user perspective, security, and Apple guidelines. RN allows writing apps that have some features and then reloads it's code and becomes completely different App. It's out of compliance with the Apple guidelines. It's not safe for users. Apple can't control that kind of apps. That's all.
All iOS browsers are based on built-in WebView. Firefox and others are just UIs for WebView from Apple. Apple want's to have everything under control. Apple can kill RN on iOS with one decision and one entry in its guidelines. Because it's Apple and they can.
IMHO, it's better to make CodeShip incompatible with latest RN on iOS because of some security mechanisms in RN framework than waiting until Apple kill RN on iOS.
UPDATE: Of course it's open source and developers, as well as CodeShip can make it possible again to run external js code on iOS but in core RN it should not be possible and be 100% compatible with Apple guidelines.
UPDATE (2): Apple is just killing https://rollout.io/ project after 3 years of its existence. I suggest to not be so confident about RN future.
Could we refrain from this type of discussion in this thread? The purpose of this thread is first and foremost to help solve the issue @sriraman has reported.
At this time, there is still nothing that clearly points towards Apple rejecting, as a whole, mechanisms such as Code Push or RN, so let's not speculate on that too much, at least not until we get a clear response from Apple. Most of us are aware of the Apple guidelines already, and as you probably know, both RN and Code Push fall under some very relevant exceptions in there.
@hilkeheremans Ok, I agree that it's maybe the wrong place for that discussion.
If the RN team is talking with Apple about its the RN compatibility with the Apple guidelines, is there a place/issue/whatever where we can track the current status of that topic? Is the RN team proactive in this or just wait until Apple starts blocking apps or something? Are there any guidelines for RN developers that were discussed with Apple?
Because I have a feeling that most of the statements I can find are based on suppositions, personal thoughts, and assumption that "if they haven't block RN yet, then it's fine, we will worry about it later".
@kapone89 We are working with @sriraman and rest of the folks to see how we can resolve this issue. At this point, we have not heard any reports of CodePush apps being banned. Note that Ios Developer License section 3.3.2 explicitly states that it is ok to update code running in JavaScriptCore or Cordova, and React Native apps use JSC.
Also note that if the purpose of the app is changed, that would be a violation. Our team at Microsoft is proactively monitoring the situation and will be updating this thread as we get updates.
@axemclion Could you send me the link that developer license because I couldn't find it.
There is nothing about it here:
https://developer.apple.com/app-store/review/guidelines/
Just
"(vii) They must use the Mac App Store to distribute updates; other update mechanisms are not allowed."
@kapone89 you need to be logged in to view it, but you can find it here: https://developer.apple.com/terms/
The only exceptions to the
foregoing are scripts and code downloaded and run by Apple's built-in WebKit framework or
JavascriptCore, provided that such scripts and code do not change the primary purpose of the
Application by providing features or functionality that are inconsistent with the intended and
advertised purpose of the Application as submitted to the App Store.
@kapone89 If you had carefully read through this thread above, you would find that both your last question and the gist of your initial remarks were already replied to. So might I suggest you re-read this entire thread?
@hilkeheremans @kelset Ok, I just thought that they removed the entry about JSC.
After reading this and following issues on the Github:
It looks like CodePush is "OK" because it doesn't change the native code of the app. BUT it is changing the code of the app. Just the code is not native. It's JS executed in JSC.
If Apple doesn't want apps to upgrade or patch itself without App Store and review process they eventually will take a look at apps that are (almost) entirely written in JS and executed by JSC.
When I'm reading how Rollout was implemented (https://rollout.io/blog/under-the-hood-2016-update/) and how it uses JSC to patch methods, I'm not so optimistic about this 3.3.2 point and JSC.
Rollout team was quite confident about their technology:
With Rollout’s SDK in your app, you can instantly push code level updates, such as bug fixes or diagnostic code, to native iOS apps in production. Yes really, and totally legit by Apple’s guidelines
I think allowing to run any external js code by RN (using JSC) is asking for troubles and waiting for a ban.
I recommend you to read this post (by Rollout):
https://rollout.io/blog/open-letter-to-apple-secure-javascript-injection-ios/
I don't think it's a perfect solution but I think that they are in troubles now and they started understanding what's the logic behind Apple guidelines and how to fulfill them somehow and save their project.
If Apple doesn't want apps to upgrade or patch itself without App Store and review process they eventually will take a look at apps that are (almost) entirely written in JS and executed by JSC.
They have been allowing webview apps to do that from beginning and React Native is not any different. You cannot update native code, which means the API exposed to javaScript can't change, like a web view based app. So it doesn't pose any more security threats than a normal web view based app.
@satya164 If it's all about security only, then maybe you are right. But I think there is also a business logic behind it etc. and it's more complicated.
And running web app inside a webview is a little bit different than rendering native view in the RN using JSC.
As @hilkeheremans posted earlier, please have speculative discussions somewhere else.
Hi,
Finally, We found out the root cause of the problem. It is not rejected because of the React Native or CodePush. We had our Manufacturer's SDK in our iOS App. They were using Amap ( Alibaba Map ) which was using the JSPatch. Since everything was in the compiled code, We were not able to figure out.
Thank you @brentvatne, @axemclion and the whole community for helping us to find the cause.
@sriraman Really glad that you found the root cause and [hopefully] managed to work around it!
@sriraman Did Apple help pinpoint the root cause of the issue above or did you figure it out for yourself?
Apple recently rejected our RN app for violations of 2.5.2. It's unlikely that the rejection is related to RN. However, I'm wondering if the appeals process was helpful in determining the issue.
Thanks!
@makeitnew No. They didn't point it out anything. They were just pointing out 2.5.2 again and again. We found the JSPatch issue before the appeal. So, We don't know how helpful the appeal will be.
You can check all the libraries which you are included to find the suspicious one and send a build to Apple after removing the suspicious libraries.
Thank you @sriraman. This is very helpful.
Most helpful comment
Hi,
Finally, We found out the root cause of the problem. It is not rejected because of the React Native or CodePush. We had our Manufacturer's SDK in our iOS App. They were using Amap ( Alibaba Map ) which was using the JSPatch. Since everything was in the compiled code, We were not able to figure out.
Thank you @brentvatne, @axemclion and the whole community for helping us to find the cause.