Case Study · Mobile

Auditing a React Native App — What the Binary Gave Up

Aug 2026 · 8 min read · Imperonlabs Research

Target class: production React Native (Hermes) mobile app, Android + iOS. Severity: informational → medium (chained context-dependent). Company and package identifiers withheld.

Mobile findings are easy to inflate and easy to dismiss. This teardown walks the real signal-to-noise: a handful of platform-hygiene issues that, on their own, are low severity — but each one lowers the cost of the next attack. Here's what a methodical static-then-dynamic pass surfaced, and how we rated it honestly.

Static pass — mapping, not magic

We pulled the package and unpacked it. The app bundled its JavaScript as Hermes bytecode, so we disassembled it to recover the logic and, more importantly, the API surface: every base URL and endpoint the app talks to. That list — not any single string — is the real prize, because the API is where the severe bugs live (see our directory-disclosure case study).

The static pass also flagged three platform issues worth confirming dynamically:

  1. allowBackup="true" in the Android manifest — the app's private data directory could be extracted via ADB backup on a non-hardened device.
  2. Certificate pinning present — good, but worth testing whether it actually held.
  3. Secure storage in use — the app stored tokens in the platform keystore/keychain wrapper, which is correct. The question was how it was configured.

Dynamic pass — the API is the target

We ran the app on an instrumented device and put it behind a proxy. Pinning initially blocked us — as designed. A single Frida hook that forces the trust evaluation to succeed unpinned it in under a minute, and from that point the app was "just an API client." Every web technique now applied to its traffic: object-level authorization, function-level authorization, business logic, token handling.

On pinning: its absence (or trivial bypass) is not, by itself, a high-severity finding — a determined attacker analyzing their own device was always going to see the traffic. We reported it as a defense-in-depth gap, not a breach. What matters is what the API does once you can see it.

The secure-storage flag

The interesting configuration detail: the secure-storage wrapper was initialized with device authentication not required. In practice that means the encryption keys protecting stored tokens were available to the app without the user re-authenticating (biometric/PIN). On a lost or compromised-but-unlocked device, or one where another process can coerce the app, the "secure" store is meaningfully weaker than it appears. Combined with allowBackup, the path from "physical access to an unlocked device" to "extracted session material" gets shorter.

We rated this medium in context — not because any single flag is catastrophic, but because they compound, and because the fix is nearly free.

What we explicitly did not inflate

Discipline in mobile reports is mostly about not overclaiming:

  • A pinning bypass with nothing sensitive behind it is informational.
  • A root/jailbreak-detection bypass that the server doesn't rely on is informational.
  • allowBackup with no sensitive local data is informational.

Each becomes real only when you show what it exposes. We verified at the API and storage layers before assigning severity.

The fixes

  • Set allowBackup="false" (and exclude sensitive files from backup rules).
  • Configure secure storage to require device authentication for key access, so tokens are gated behind biometric/PIN.
  • Keep pinning, and treat its bypass as expected — never let a server-side control depend on a client-side check the user's own device can disable.

The lesson

A mobile audit is a funnel: static to map the surface, dynamic to reach the API, and honest severity so the report is trusted. The headline findings almost always live in the API the app reveals — the platform-hygiene issues are the multipliers, not the main event.