Every mobile assessment I run finds a variation of the same handful of issues. Not because teams are careless, but because the mobile release checklist a team inherits is about crash-free rates and store metadata, and nobody ever added the security half. This is the security half.
Work through it in the order below. It follows the way an attacker actually approaches an app: pull the package apart, look at what it wrote to disk, watch what it says on the network, then go after the server that trusts it.
1. What ships inside the package
Assume the binary is public. It is: anyone can download it and unpack it in minutes. Everything compiled in is readable, including strings you assumed were buried in a Dart snapshot or a native library.
- No API keys, secrets or private endpoints in source, resources or build config. If a third-party SDK needs a key, it must be one that is safe to be public, with server-side restrictions applied.
- Release builds are not debuggable, and debug menus, feature flags and test screens are stripped rather than hidden.
- Obfuscation is enabled, with the mapping file archived — obfuscation is not a security control, but it raises the cost of casual reversing.
- No staging or internal hostnames left reachable in the production build.
- Third-party SDKs reviewed for what they collect and transmit; one analytics library sending full request bodies is a data-protection incident waiting to be reported.
2. What lands on the device
Local storage is the second most common source of critical findings, and almost all of it comes down to storing something long-lived that did not need to be stored at all.
- Session tokens, refresh tokens and PII are in Keystore or Keychain-backed encrypted storage — never plain SharedPreferences or NSUserDefaults.
- Databases and cached API responses containing personal data are encrypted, and cleared on logout along with cookies and WebView storage.
- Auto-backup excludes sensitive files (Android allowBackup rules, iOS backup exclusion attributes).
- Logs in release builds contain no tokens, request bodies or personal data.
- Sensitive screens set FLAG_SECURE or the iOS equivalent so they do not appear in the app switcher, screenshots or screen recordings.
3. What crosses the network
- TLS everywhere, with cleartext traffic disabled at the platform level rather than by convention.
- Certificate or public-key pinning implemented in the HTTP client, with a documented rotation plan so pinning does not brick the app at renewal.
- The app fails closed when interception is detected, rather than silently falling back to an unpinned client.
- No sensitive data in URLs or query strings, where it lands in logs and analytics.
Expect pinning to be bypassable on a rooted device with Frida — that is fine and expected. The control exists to stop opportunistic interception on hostile networks, not to defeat a determined attacker with physical access. Problems start when pinning is the only thing protecting an endpoint that should have been authorising the request itself.
4. Platform hardening
- Exported activities, services, receivers and content providers reduced to the minimum, with permission checks on what remains.
- Deeplinks and custom URL schemes validate their parameters and require authentication before acting on them.
- WebViews disable JavaScript where unnecessary, block file access, and never load untrusted URLs.
- Root and jailbreak detection present where the threat model justifies it — as telemetry and friction, not as a gate you believe in.
- Biometric authentication is bound to a Keystore key operation, not a boolean returned to the app.
5. The server the app trusts
This is where the severe findings live. The app is a client; controls implemented only in the client do not exist.
- Every endpoint enforces authorisation per object, tested with a second account. If user A can fetch user B's record by changing an ID, nothing else on this list matters.
- Roles, prices, balances and status fields sent by the client are ignored server side.
- Rate limits exist at the gateway on authentication, OTP and password reset endpoints.
- Responses return only what the client needs — no internal identifiers, no other users' contact details, no debug fields.
- Token lifetimes are short, refresh rotation is implemented, and logout actually revokes server side.
How to use this before a release
- Run the package checks in CI — secret scanning on the built artefact, not only on source.
- Do the storage and network checks once per quarter on a physical device, and after any change to authentication.
- Test the authorisation matrix on every new endpoint, as part of the pull request that adds it.
- Book a full assessment before any release that adds payments, personal data or a new authentication flow.
None of this replaces a penetration test — it removes the findings that make one expensive. When the obvious issues are already closed, the testing hours go into logic and chaining, which is where the findings worth paying for come from.