Hacker News new | ask | show | jobs
by jameside 3395 days ago
Hi, I work on Expo (YC S16) and also am a core contributor to React Native.

Apple's message reads to me that they're concerned about libraries like Rollout and JSPatch, which expose uncontrolled and direct access to native APIs (including private APIs) or enable dynamic loading of native code. Rollout and JSPatch are the only two libraries I've heard to be correlated with the warning.

React Native is different from those libraries because it doesn't expose uncontrolled access to native APIs at runtime. Instead, the developer writes native modules that define some functions the app can call from JavaScript, like setting a timer or playing a sound. This is the same strategy that "hybrid" apps that use a UIWebView/WKWebView have been using for many years. From a technical perspective, React Native is basically a hybrid app except that it calls into more UI APIs.

Technically it is possible for a WebView app or a React Native app also to contain code that exposes uncontrolled access to native APIs. This could happen unintentionally; someone using React Native might also use Rollout. But this isn't something specific to or systemic about React Native nor WebViews anyway.

One nice thing about Expo, which uses React Native, is that we don't expose uncontrolled or dynamic access to native APIs and take care of this issue for you if your project is written only in JS. We do a lot of React Native work and are really involved in the community and haven't heard of anyone using Expo or React Native alone having this issue.

5 comments

I do wonder why, if they're _really_ fine with that, why they're not fine with browsers with different rendering engines on iOS.

Since they're not, I wouldn't have _too much_ faith in other things not being rejected.

Apple has been fine with WebViews in apps since the beginning of the App Store, including WebViews that make calls to native code, like in the Quip app. WKWebView even has APIs for native and web code to communicate. It's OK for a WebView to call out to your native code that saves data to disk, registers for push notifications, plays a sound, and so on.

React Native is very much like a WebView except it calls out to one more native API (UIKit) for views and animations instead of using HTML.

What neither WebViews nor React Native do is expose the ability to dynamically call any native method at runtime. You write regular Objective-C methods that are statically analyzed by Xcode and Apple and it's no easier to call unauthorized, private APIs.

With Expo all of the native modules are safe to use and don't expose arbitrary access to native APIs. Apps made with Expo are set up to be good citizens in the React Native and Apple ecosystems.

Well, let's be clear here. I use React Native, and it would take me about 30m to write a bridged React Native method that could execute ObjC code dynamically, including accessing all the private APIs you could want.
Yeah. Objective-C is definitely really flexible. From my reading of the warning, the important thing is not to execute Objective-C dynamically or access private APIs regardless of whether you're using React Native, Cordova, a WebView, or even a bare iOS app.
Simple: JIT compilers are banned and so that excludes any modern browser's JavaScript implementation from iOS. But anyone using Apple's JavaScriptCore has nothing to fear.
The rules explicitly forbid any HTML renderer or JS interpreter aside from WebKit, JIT or no JIT. I believe all the popular third party browsers today still use the non-JIT UIWebView rather than WKWebView because the former gives you more control over the request cycle
Chrome uses WKWebView on iOS, so it's basically safari with a different UI (so does firefox on iOS)
Yes, I know. I was saying the OP of this thread was wrong because it's not UIWebView, it's WKWebView with a JIT :).
Webkit is the only acceptable browser engine on iOS. Firefox and Chrome both use webkit on iOS.
How can you ship an app with access to private APIs? There is a private API usage scanning before you can submit for review.
The scanner isn't foolproof. You could fool it if you obfuscate your calls to performSelector well enough, for example

if jsonResponseFromYourBackend contains:"runThis" then performSelector:json["runThis"]

and make sure you don't send a runThis param while the app is in review.

Unfortunately for Apple's app review process, Apple's own objective-C language and runtime has very strong dynamic reflection capabilities.

Apple could potentially close any loopholes here by scanning new apps for their API usage, checking for any 'bad' calls, and then writing the remaining discovered calls into a permissions file that is delivered with the app in the store.

At runtime, any API calls made by the app are checked against this file; if a new API call is found, then it must have escaped Apple's code scanning logic. The API call can be rejected and logged for Apple to improve their scanner.

This is a great idea actually. Actually, isn't google already doing this via SELinux? You give the app a manifest of calls it's allowed to make, and if the call isn't in the manifest the call gets rejected?
SELinux is not that strong. It works on kernel syscall boundaries and some parameters thereof, and those aren't particularly fine grained. Service access is governed by a separate Google API, for example.

Moreover, any random app cannot enhance SELinux policy of the system.

There are many legitimate uses of calling methods and functions using reflection. Expecting to hit all of them in a short review process is comically optimistic for anything but the simplistic of apps.

Your suggestion of enforcing this also makes no sense from performance or privacy standpoint.

Fair warning that I'm not familiar with Swift

Obvious (to me) idea: have the private API access stored as data sent from the server at runtime, rather than code in the reviewed app. Basically the equivalent of eval()-ing a string for front-end javascript code.

James, do you know if they're going to go agains the Exponent app per-se?
Oh, no, I don't have reason to believe so.
Rather than a speculating on what really boils down to semantics once ToS is involved maybe someone could actually try submitting an app and reporting back on whether it triggers the same failure?
I haven't heard any reports of Expo developers or React Native developers (who aren't using Rollout or JSPatch) getting this warning.
This needs a little elaboration. Cached Javascript in any hybrid app is a security hole because that can be exposed through a jailbreak. Depending on how much of your business logic you've pushed into the JS layer to enable that "80% code sharing" that makes managers go all tingly you may be exposing all kinds of things - cached access tokens, API keys and whatnot - to anyone who wants to install your app and mine its secrets.
Huh? Are you saying that the security hole is that a user could see stuff in the memory of their own phone?
Or someone that steals your phone, or picks it up when you lose it somewhere. Yes, there's the lock screen and passcode but...

http://www.wikihow.com/Bypass-iPhone-Passcode

At that point your phone is fucked, anyhow. If you've lost physical control of the device and an attacker has broken the lock, you're compromised in much bigger ways.
"This bypass won't work on iPhones running iOS 9.3 and up"
How is this any different from native code? Afaik, you can access the native compiled source code of an app on a jailbroken phone. Sure it's more of a pain to parse through but security through obscurity isn't security.
Definitely, running "strings" on a binary is about as easy as finding the JS for a hybrid app (WebViews or React Native). Depending on your experience it could be easier to extract API keys from an IPA than from JS.

In either case the root issue is about sending secrets like an unscoped API key to the client. "Client secret" is an oxymoron in this context regardless of the programming language.

You'd have to know a few things first, like (1) the IPA is a ZIP file, (2) the ZIP file is actually of a directory and (3) you can dump the actual code in the JS files (if they're in the bundle directory) much easier than you can look for strings from the binary that might look like an API key.

The API key is actually the least of the hazards, since you can hide that in the keychain. Having source code for your business logic shipping in your app is not good; having it be hackable business logic (by changing the JS in place) is very not good.

Shipping source code with business logic (assuming the definition of source code includes obfuscated JS) is how the entire web works today! With WASM the code that is shipped will be even further away from the original source code and really not so different from downloading ARM from the App Store or Java/ART bytecode from the Play Store.
Not the entire web. eBay and Amazon don't put all their algorithms in the browser; PayPal doesn't either. They hide their company jewels behind their API where they're a little more secure. What you see in the browser is the presentation layer code.

Hybrid apps could achieve that same kind of relative business logic security, but at the cost of pushing more and more of the actual business logic behind an API and not in the JS in the app. At that point, the benefits of code sharing (such as they are) get fewer and fewer since it's really pretty easy to write API code in Objective C, Swift, Java or Kotlin.

You aren't "parsing" through compiled (and linked) code; you're decompiling it, which is a much trickier thing to do and get right. Having your app logic in Javascript in the sandbox cache is just serving it up on a plate.
I'm having trouble seeing this as a valid argument when applied to cached JavaScript within a mobile browser. The same security practices apply.
They do, and it's for this reason that the really important stuff isn't in the web page at all - it's behind the company's API. The "business logic" that you can safely push out to a browser or hybrid app is somewhat limited, which means there is a hard upper limit on the real code sharing savings you can get with hybrid apps versus full native.
You as an end user jailbreaking your own phone is not a "security hole".

I'm not aware of any non-tethered jailbreak for iOS 10

I think he's talking about application wide (not user specific) "secrets" in the javascript layer.
Yes, I am. As for untethered jailbreaks, http://pangu8.com/10.html mentions a few.
Yes it is, jailbreaking bypasses critical security features of your phone. Granted, it's required to run certain kinds of software but there are better ways to run your own code on your phone (like getting your own developer certificate) that preserve the security model.