close search bar

Sorry, not available in this language yet

close language selection

Considerations before moving away from native apps

Vineeta Sangaraju

Mar 18, 2024 / 5 min read

To some, native applications are rudimentary. Why write an application specific to one platform when you can build one that is cross-platform compatible? After all, expanding the user base is one of the most fundamental objectives for software development teams.

Doing this quickly with the current “build apps for any screen” approach is the obvious choice. A single codebase that can be deployed as a mobile, web, desktop, and embedded application cannot get any better from a business point of view.

But let’s take a step back and look at what we are leaving behind. Before jumping on the hybrid mobile apps bandwagon, let’s review the native features that simply work better. In this blog post, we’ll look at the popular Flutter framework to understand the difference. We will then explore how these features can be replicated in hybrid applications, or at least how to minimize the negative effects of the absence of these features.


Native app hardware

Backed by hardware components like Secure Enclave or Trusted Execution Environment, native frameworks offer security that includes tightly coupled cryptography. This ensures the confidentiality that sensitive operations require. Further, native apps have direct access to hardware-specific APIs. This, coupled with a strong permission system, allows fine-grained control and isolation between applications and hardware.

Native apps also seamlessly integrate hardware features with platform-specific security features such as biometric authentication. For example, when implementing biometric authentication, the secure approach involves using a cryptographic signature from the keychain as well as wiping that signature upon biometric re-enrollment. This accounts for scenarios in which, for example, a new user enrolls new biometrics on the same device, potentially leading to authentication bypass attacks. Native operating systems like iOS provide a defense against such security implications through options like “setInvalidatedByBiometricEnrollment.” Cross-platform frameworks and their libraries may not follow suit. For example, the Flutter library for biometric authentication, “local_auth,” does not support this option as of today.

Communication

Typically, native operating systems are held to higher security standards. Hence, they tend to have better support for the latest protocols and cryptographic algorithms compared to plugins. For example, on Android, communication between applications is performed via tried-and-tested native APIs with additional security options such as the “verifyURL” option. This option verifies the target application before launching a page through an Android Intent, thereby avoiding malicious and unauthorized intents. Flutter’s popular library for handling intents, “android_intent_plus,” may not emulate such features or carry over such APIs, leaving the door open for interapplication attacks.

Platform features

In general, frameworks backed by native operating systems such as Swift actively patch vulnerabilities, update software, and provide secure alternatives to developers. As part of these updates, APIs considered risky are communicated as deprecated. Developers are encouraged, if not forced, to upgrade to better and safer APIs. Open source libraries of cross-platform frameworks that expose such APIs may not do the same, at least not immediately or until subsequent releases. This creates a tiny but dangerous attack surface.

So what is the problem, really?

These native features are theoretically easy to “lift” and expose in cross-platform frameworks. To utilize them, developers need only check if the framework or its open source community offer these APIs via plugins. This usually involves three steps. First, expose the APIs in native code. Second, register these APIs as a plugin. Third, define the plugin’s APIs using the hybrid framework’s code. This creates a bridge between the native and hybrid code. Consequentially, this bridge can make or break the security of the hybrid application.

Flutter

Let’s take Flutter as an example to understand this bridge. Dart, the language that Flutter employs, does not need the platform’s SDK, per se. Typically, the native operating system uses ViewControllers to display different screens of an application. A Flutter app is hosted in one such ViewController. Flutter provides platform channels that help communicate with this ViewController. Access to native features is made possible through these channels and thus, these channels act as the bridge between native and hybrid code. Every channel has two ends, the receiver and the sender. In this case, the receiver is the Swift or Java code, and the sender is the Dart code. Depending on the functionality needed for an application, such channels may already exist as a Flutter community plugin. Alternatively, one could build it from scratch.

Sounds simple; what could go wrong?

Turns out, a “bad” bridge (i.e., a “bad” channel in the case of Flutter) could cause plenty of insecure effects. Not bad development practices such as unvalidated user input—those scenarios apply the same to native applications as they do to cross-platform applications. But the foundational concepts that form the backbone of cross-platform frameworks diminish the tried-and-tested protection of a native operating system. These concepts bring cross-platform compatibility, but they jeopardize security through loose coupling with every supported platform. Subsequently, sole reliance on cross-platform plugins introduces loopholes.

  • The bridge is an extra layer of code. Going by the “keep it simple” principle, it is more likely to introduce vulnerabilities.
  • A plugin marketed toward a set of platforms introduces security gaps if not thoroughly tested against the latest platform’s updates.
  • While plugins provide access to native features, the level of control might be limited. This can impact the application's ability to implement certain security measures effectively, making bypasses easy.
  • Updates in the native world are more frequent, so keeping a hybrid application heavily reliant on third-party plugins is challenging.
  • Hybrid applications often rely on communal knowledge, i.e., third-party plugins that help access native features. These plugins might have vulnerabilities, security flaws, or outdated dependencies that could be exploited by attackers.  
  • Most hybrid apps are likely created through conversion—converting a native application into a hybrid one—leaving room for incomplete or insecure use of framework APIs.

Hybrid apps are the future

Despite these challenges, hybrid applications without a doubt win the race due to the ease and speed they offer. But can you also have security? By incorporating additional or alternate measures, a trade-off between ease and security can be achieved.

  • Pick the plugin wisely. Does it follow basic application security principles? Review the platform-specific APIs corresponding to the chosen plugin.
    • Is the native API deprecated? Has the library taken this into consideration?
    • Does the library expose all the native options—importantly, the ones related to security?
    • Does the library API work as intended on all platforms?
    • Does the library assign an insecure default when the native platform does not?
  • Build your own plugin securely.
  • Fork a plugin and support all native features by referring to the platform documents.
  • Periodically review the dependencies and libraries via static code analysis and dependency analysis.    
    • Run tools that identify insecure implementations.
    • Review dependencies and update as necessary.

The ecosystem of cross platform frameworks for mobile development is continually growing, making it difficult to manually ensure the safety and security of these libraries as part of the SDLC. With the latest releases in Synopsys SAST solutions, mobile applications built using native or hybrid languages and frameworks can be automatically assessed against these measures. Checks for API safety, hardcoded secrets, and, IaC misconfigurations, are a core component of the engine that forms the backbone of these solutions.

Continue Reading

Explore Topics