An app that has been running for a few years usually wasn't designed with security in mind. It was built quickly, then more features were added, vendors changed, and nobody had time to look at the whole picture. As long as nothing happens, everything seems fine. The problem is that you usually only learn about a vulnerability when someone exploits it.
The consequences are concrete. A customer data breach means a GDPR obligation to notify the supervisory authority within 72 hours, informing users and a loss of trust. Leaked keys to third-party services mean paying for someone else's usage. API abuse can run up thousands in SMS charges over a single weekend.
This article describes ten problems we find most often when we audit or take over older mobile apps — how to detect them and how to fix them.
How we check an app's security
The reference point is OWASP's mobile standards: the OWASP Mobile Top 10 list of the most common risks (updated in 2024), and the detailed MASVS requirements together with the MASTG testing guide. In practice, an audit has several parts:
- static analysis — reviewing the source code and the built app (APK, IPA), including with tools such as MobSF,
- decompilation — checking what someone sees when they download the app from the store and unpack it,
- network traffic analysis — inspecting the app's communication with the server through a proxy,
- API testing — checking whether the server trusts the app more than it should,
- a review of configuration and access — store consoles, Firebase, servers, signing keys.
The last point is often skipped, and in older apps it's frequently the most important one.
1. Secrets stored in the app's code
Keys to payment, maps, SMS or cloud APIs written directly into the app's code. This is the most common audit finding — and one of the easiest to exploit. Anyone can download the app from the store, unpack it with a free tool and search it for keys in a few minutes. Code obfuscation (such as R8 on Android) makes the logic harder to read, but it won't hide a key.
How to fix it: treat every key found in the app as exposed and rotate it. Move keys that grant access to data or cost money to the backend — the app asks the server, and the server calls the third-party service. Keys that have to stay in the app (for maps, for example) should be restricted in the provider's console to your app's identifier and signing certificate.
2. A backend that trusts the app
The most serious problem, because it affects every user's data at once. A typical scenario: the app shows users only their own orders, but the server returns any order if you change the number in the request from /orders/1023 to /orders/1024. Or the admin panel is hidden in the app, but its endpoints work for any logged-in user.
A mobile app is in the user's hands — every request it sends can be inspected and modified. Hiding a button in the interface is not a security measure.
How to fix it: every endpoint has to check on the server whether the logged-in user is allowed to access that specific resource. This is backend work, best combined with automated tests — so that a new endpoint without a permission check can't get through review.
3. Data and tokens stored unencrypted
A login token, personal data or order history saved in a plain settings file — SharedPreferences on Android, UserDefaults on iOS, AsyncStorage in React Native. On a rooted or jailbroken phone, from a backup or through a malicious app, this data can be read.
How to fix it: store tokens and sensitive data in the system's secure storage — the Keystore on Android and the Keychain on iOS (for example flutter_secure_storage in Flutter or react-native-keychain in React Native). While you're at it, check whether the app really needs to store everything it keeps on the phone. Data that isn't there can't leak.
4. Sessions that never expire
A token issued at first login works for years, and logging out only removes it from the phone — on the server it's still valid. If the token leaks (from logs, a backup, intercepted traffic), it grants access to the account with no time limit. Changing the password often changes nothing.
How to fix it: a short-lived access token and a longer-lived refresh token, server-side session invalidation on logout and password change, and — in apps handling sensitive data — the option to log out of all devices.
5. Insecure network communication
In new apps, encrypted HTTPS is the standard — Android has blocked unencrypted traffic by default since version 9, and iOS has required HTTPS for years. In older apps, though, we regularly find exceptions: unencrypted traffic allowed for the whole app (usesCleartextTraffic, NSAllowsArbitraryLoads), added "temporarily" for testing at some point, or server certificate validation switched off in code that was only supposed to run in the development build.
How to fix it: remove the exceptions, and if one is genuinely needed, limit it to a specific domain. Certificate pinning adds protection in financial and medical apps, but it needs a procedure for when the server certificate is replaced — implemented badly, it can lock out every user overnight.
6. Outdated libraries with known vulnerabilities
The app has dozens of dependencies, some not updated in years, some abandoned by their authors. Vulnerabilities in popular libraries are publicly documented, so exploiting them doesn't take much expertise.
How to fix it: list your dependencies and check them for known vulnerabilities — npm audit, OWASP Dependency-Check, Snyk — and on an ongoing basis use Dependabot or Renovate, which propose updates automatically. Replace abandoned libraries with maintained ones. If an update requires upgrading the whole framework, that's a separate project — we described it in our article on updating an old Flutter or React Native app.
7. Personal data in logs and crash reports
The app writes full server responses to its logs, including tokens, addresses and phone numbers. Crash reports in Crashlytics or Sentry contain users' email addresses, and analytics events carry data that shouldn't be there. This is a security problem and a GDPR problem at the same time: personal data ends up in third-party services, often without a legal basis and without an entry in the record of processing activities.
How to fix it: turn off verbose logging in production builds, filter sensitive data before crash reports are sent, and review which parameters your analytics sends. In crash reports, identify users by an internal ID, not an email address.
8. Development leftovers and open doors
This category covers several similar problems:
- a hidden test menu with a server switch or a "log in as any user" option, available after tapping the logo a few times,
- test server addresses in production code — and behind them a test server with a copy of real data and weaker protection,
- Android components exposed to other apps (exported) without need and without permissions,
- a WebView that exposes the app's native functions to web pages or opens any URL passed in from outside,
- deep links that perform actions without checks, such as changing account settings.
How to fix it: strip test features from the production build at build time, not by hiding them in the interface. Review the Android manifest and mark everything that doesn't need to be exposed as not exported. Treat data passed in through deep links and WebViews as untrusted user input.
9. An API with no protection against abuse
Login, sign-up and SMS code endpoints with no limit on attempts. The consequences aren't just password brute-forcing. SMS pumping is increasingly common: a bot sends thousands of verification code requests to premium-rate numbers, and the app owner pays the SMS bill. In a single weekend you can lose more than a year of app maintenance costs.
How to fix it: limits on attempts per number, IP address and device, blocking SMS to countries you don't operate in, and alerts on sudden cost spikes. It's also worth checking that requests come from your genuine app — that's what the Play Integrity API, App Attest on iOS and Firebase App Check are for.
10. Access nobody remembers
Former developers still have access to Google Play Console, App Store Connect, the Firebase project and the server. The database password hasn't changed in five years and is known to everyone who ever worked on the project. The app's signing key sits on the personal laptop of someone the company no longer works with.
No code analysis tool will detect this problem, and in apps taken over from other vendors we find it almost every time.
How to fix it: list every account and service, remove access for people no longer on the project, change the passwords and keys they may have known, and enable two-factor authentication. You'll find an access checklist in our article on taking over a project from another software house, and what to do when all access sits with one person in our article on what to do when your developer disappears.
Where to start: priorities
Problem Risk Urgency A backend that trusts the app every user's data leaking immediately Secrets in the app's code costs, access to data in third-party services immediately — rotate the keys Former collaborators' access takeover of the app, accounts and data immediately API without limits SMS costs, account takeover within a few weeks Non-expiring sessions, unencrypted data takeover of individual accounts within a few weeks Outdated libraries depends on the specific vulnerability an update plan Data in logs, debug leftovers GDPR, making attacks easier with the next releaseSome fixes — rotating keys, revoking access, adding API limits — don't need a new app version in the store and can be rolled out within days. Changes to the app itself have to be planned together with a release.
What you can check yourself
Without technical knowledge, you can establish a few things in an hour:
- who has access to Google Play Console, App Store Connect, Firebase and the server — and whether all of them still work on the project,
- whether two-factor authentication is enabled on those accounts,
- who holds the Android app signing key and whether the app uses Play App Signing,
- whether your SMS, cloud or third-party service bills have grown in recent months for no obvious reason,
- when someone last updated the app's libraries.
If the answer to any of these is "I don't know", that's a good starting point for an audit.
How long a security audit takes
A review of a typical mobile app with a backend — static analysis, decompilation, network traffic, API tests and an access review — usually takes from a few days to two weeks, depending on the number of features and whether the audit covers the backend. The result is a list of issues with a risk rating and a recommended fix, ordered by urgency. We write more about the scope of a code review in our article on source code audits. Financial apps have additional regulatory requirements — we described them in our article on apps for fintech.
Summary
Most security problems in older mobile apps don't come from sophisticated attacks, but from decisions made in a hurry: a key written into the code, a server that trusts the app, a token saved in a plain file, access nobody revoked. The good news is that the most dangerous of them — keys, server-side permissions and access — can be fixed quickly, often without releasing a new app version.
If you don't know what state your app is in, request a free app analysis — we'll point out the most serious risks. We run full security reviews as part of our audits and consulting, and regular updates and access control as part of our mobile app maintenance service.