A mobile app penetration test checks whether an attacker can read, change or abuse your app and the APIs it calls. For a protected app, the checklist has a second job: testers must be able to do their work without the team switching protections off in the build that ships. This article gives a working checklist for Android and iOS apps in three parts: choosing the right test environment for each question, scoping exceptions so testing and protection coexist, and defining release criteria that make a result repeatable. It follows the OWASP MASVS control groups and treats Android and iOS as separate platforms throughout.
Test environment: devices, emulators and cloud labs
No single environment answers every question. Emulators and simulators are fast and easy to reset; physical devices behave like your users’ phones; cloud labs give breadth. Protected apps add a twist: they are built to notice emulators, debuggers and instrumentation, so the environment itself becomes part of the test.
| Environment | Good for | Limits for protected apps |
|---|---|---|
| Android emulator | Static checks, traffic inspection, fast resets | The protected build is expected to flag the emulator and respond |
| iOS Simulator | UI flows and logic in debug builds | Runs apps built for the simulator, not the signed App Store build |
| Physical Android device | Release builds, hardware-backed keys, root detection | Needs a clean device and a rooted one kept strictly apart |
| Physical iOS device | Release builds, Keychain, jailbreak detection | Jailbreak availability depends on the iOS version in use |
| Approved cloud device lab | Breadth across models and OS versions | Shared devices; confirm data handling and reset policy first |
The iOS row deserves emphasis. The Simulator runs code compiled for the simulator, so it cannot tell you how the signed App Store build behaves. Anything that concerns binary protection, runtime checks or Keychain on iOS needs a physical device.
Policy fit: exceptions without switching protection off
A common shortcut in penetration testing is also a risky one: disabling protections so testers can attach a proxy or a debugger, then forgetting to switch them back on. A better approach is a test build with narrow, written exceptions.
Protection switched off
- One build for testing and release
- Protections disabled by a flag someone must remember
- Findings describe an app no user will ever run
- Risk of shipping the unprotected build
Scoped test build
- Separate test build, clearly labelled and signed differently
- Only the exceptions the test plan needs, each written down
- Release build tested separately, with every protection on
- Exceptions are kept out of the release pipeline
Otukan lets teams produce test builds with scoped exceptions while the release build keeps every protection enabled, and it reports the runtime and integrity events it raises during a test run. For how that works in practice, see testing protected apps.
Platforms already offer this pattern for traffic inspection. From Android 7.0 onwards, apps that target it do not trust user-installed certificate authorities by default. A network security configuration can allow them only in debuggable builds, so a proxy works during testing and the release build is unaffected:
<!-- res/xml/network_security_config.xml,
referenced from the manifest with android:networkSecurityConfig -->
<network-security-config>
<debug-overrides>
<trust-anchors>
<!-- Applies only when android:debuggable is true -->
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
iOS has no direct equivalent. Testers install and trust a proxy’s certificate profile on a dedicated test device, and any certificate pinning in the app needs its own exception in the test build. Handle each platform on its own terms.
Agree the scope
List the questions, platforms and exceptions the test needs.
Build for testing
Produce a separately signed test build with only those exceptions.
Test and record
Run the checklist and log every event the app raises.
Retest the release build
Repeat the resilience checks on the release build with every protection on.
Close the exceptions
Confirm no test-only setting exists in the release pipeline.
Release criteria: events, journeys and diagnostics
A finding is only useful if someone else can reproduce it. For each item on the checklist, record three things: the expected event (what the app or backend should raise), the user journey it was tested in, and the diagnostic result (logs, traffic captures, screenshots). The table below groups the checklist by OWASP MASVS control group.
| MASVS group | Checklist item |
|---|---|
| MASVS-STORAGE | No tokens, keys or personal data in plain files, logs or backups |
| MASVS-STORAGE | Sensitive screens excluded from screenshots and the app switcher |
| MASVS-CRYPTO | Keys generated and held in Android Keystore or iOS Keychain |
| MASVS-AUTH | Sessions expire and cannot be replayed from another device |
| MASVS-AUTH | Sensitive actions require step-up on the server, not only in the app |
| MASVS-NETWORK | TLS enforced on every connection; pinning behaves as designed |
| MASVS-PLATFORM | Exported components, deep links and web views accept only expected input |
| MASVS-CODE | Release build is not debuggable and ships no test settings |
| MASVS-RESILIENCE | Repackaged copy is detected and the configured response applies |
| MASVS-RESILIENCE | Hooking, debugging and emulators are detected on the release build |
| MASVS-RESILIENCE | Root and jailbreak signals fire on the modified device only |
| MASVS-PRIVACY | Data collection matches the privacy declarations in the stores |
Counts come from the checklist table above. Resilience carries the most items because protected apps add runtime checks that must be proven on the release build.
Resilience items need the most care, because they are the ones a scoped test build changes. Run them twice: once on the test build to confirm the exceptions behave, and once on the release build to confirm every protection responds as configured.
Sign-off checklist
- Every test states its environment: emulator, simulator, physical device or cloud lab.
- Each exception in the test build is written down, and none exists in the release pipeline.
- Resilience checks were repeated on the release build with every protection on.
- Install, update and first launch on a clean Android and a clean iOS device raise no events.
- Every finding lists its expected event, user journey and diagnostic evidence.
- Android and iOS results are reported separately, never merged into one verdict.