Security Isn't Optional Anymore
We started offering mobile app security audits about two years ago after a client's React Native app was reverse-engineered by a competitor who extracted their entire API key set and pricing logic. Since then, we've audited 28 mobile apps — a mix of client apps and apps we inherited from other development teams. The same seven vulnerabilities appear in roughly 80% of them.
1. API Keys in the JavaScript Bundle
This is the most common and most dangerous one. We see API keys, secret tokens, and even database connection strings hardcoded in the JavaScript source. In React Native, your JS bundle is a plain text file inside the APK/IPA. Extracting it takes about 30 seconds with a tool like apktool. We found Google Maps API keys with no restriction (rack up unlimited bills), Firebase admin credentials, and in one memorable case, a Razorpay secret key that would allow creating unauthorized transactions.
The fix: use environment variables loaded at build time for non-sensitive configuration. For secrets that the app needs at runtime, use a secure configuration endpoint — the app authenticates, then fetches secrets over HTTPS, and stores them in the device's secure keychain (iOS Keychain or Android Keystore). Never, ever put a secret key in your source code.
2. Insecure Data Storage
We regularly find sensitive data stored in AsyncStorage, which is an unencrypted key-value store. On a rooted/jailbroken device, anyone can read AsyncStorage contents. We've found auth tokens, personal information, and even cached financial data in plain text. One fintech app stored the user's PAN number and bank account number in AsyncStorage for "quick re-entry."
The fix: use react-native-keychain for authentication tokens and sensitive identifiers. It uses the iOS Keychain and Android Keystore, which are encrypted and protected by the device's hardware security module. For larger datasets that need encryption, use react-native-encrypted-storage or SQLCipher.
3. Missing Certificate Pinning
Without certificate pinning, a man-in-the-middle attack on a compromised WiFi network can intercept all API traffic, even over HTTPS. The attacker installs their own root certificate and proxies all requests through their server. We demonstrated this to a banking client by intercepting their app's API calls on a coffee shop WiFi network using Charles Proxy. Their security team went white.
The fix: implement SSL certificate pinning using react-native-ssl-pinning or TrustKit. Pin against the certificate's public key (not the full certificate) to avoid breaking the app when certificates rotate. And have a fallback mechanism — if pinning fails, show a security warning rather than silently failing open.
4. Improper Session Management
Auth tokens that never expire, no refresh token rotation, sessions that persist after password change. We see all of these regularly. One app had JWT tokens with a 30-day expiration and no revocation mechanism — if a token was stolen, the attacker had 30 days of access even if the user changed their password.
The fix: short-lived access tokens (15-30 minutes) with refresh token rotation. When a refresh token is used, the old one is invalidated and a new one is issued. Maintain a token blacklist on the server side for immediate revocation. And invalidate all sessions when the user changes their password.
5. Lack of Root/Jailbreak Detection
For apps that handle financial transactions or sensitive data, running on a rooted/jailbroken device is a significant risk. Rooted devices can bypass app sandboxing, read encrypted files, and inject code into your app's process. Most of the apps we audit have no detection whatsoever.
6. Debug Mode in Production Builds
We've seen production APKs with React Native's dev menu enabled, console logs printing sensitive data, and even the Hermes debugger accessible. Always verify your release build configuration strips debug features.
7. Inadequate Input Validation
Client-side validation is not security. Every input — form fields, deep link parameters, push notification payloads — must be validated on the server side. We've exploited deep link handlers that passed unsanitized parameters directly to API calls, allowing us to access other users' data by modifying the deep link URL. Always validate and sanitize every input, even from sources you think you control.