Security research

Mobile app penetration testing checklist

A penetration testing checklist for protected Android and iOS apps: choosing test environments, scoping exceptions safely and setting release criteria.

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.

EnvironmentGood forLimits for protected apps
Android emulatorStatic checks, traffic inspection, fast resetsThe protected build is expected to flag the emulator and respond
iOS SimulatorUI flows and logic in debug buildsRuns apps built for the simulator, not the signed App Store build
Physical Android deviceRelease builds, hardware-backed keys, root detectionNeeds a clean device and a rooted one kept strictly apart
Physical iOS deviceRelease builds, Keychain, jailbreak detectionJailbreak availability depends on the iOS version in use
Approved cloud device labBreadth across models and OS versionsShared 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
Testers get what they need, and the release build stays exactly what users receive.

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.

  1. Agree the scope

    List the questions, platforms and exceptions the test needs.

  2. Build for testing

    Produce a separately signed test build with only those exceptions.

  3. Test and record

    Run the checklist and log every event the app raises.

  4. Retest the release build

    Repeat the resilience checks on the release build with every protection on.

  5. Close the exceptions

    Confirm no test-only setting exists in the release pipeline.

A test run that keeps exceptions contained. Each step leaves a record the next step depends on.

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 groupChecklist item
MASVS-STORAGENo tokens, keys or personal data in plain files, logs or backups
MASVS-STORAGESensitive screens excluded from screenshots and the app switcher
MASVS-CRYPTOKeys generated and held in Android Keystore or iOS Keychain
MASVS-AUTHSessions expire and cannot be replayed from another device
MASVS-AUTHSensitive actions require step-up on the server, not only in the app
MASVS-NETWORKTLS enforced on every connection; pinning behaves as designed
MASVS-PLATFORMExported components, deep links and web views accept only expected input
MASVS-CODERelease build is not debuggable and ships no test settings
MASVS-RESILIENCERepackaged copy is detected and the configured response applies
MASVS-RESILIENCEHooking, debugging and emulators are detected on the release build
MASVS-RESILIENCERoot and jailbreak signals fire on the modified device only
MASVS-PRIVACYData collection matches the privacy declarations in the stores
Checklist items per MASVS group
Resilience3Storage2Auth2Crypto1Network1Platform1Code1Privacy1

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.

Common questions

  • Can we penetration test a protected iOS app on the Simulator?

    Only for UI flows and logic in debug builds. The iOS Simulator runs code compiled for the simulator, so it does not show how the signed App Store build behaves. Anything that concerns binary protection, runtime checks or the Keychain on iOS needs a physical device.

  • Should we switch protections off so testers can work?

    No. Disabling protections risks shipping the unprotected build and produces findings about an app no user will run. Use a separately signed test build with only the exceptions the test plan needs, each written down. Otukan lets teams produce test builds with scoped exceptions while the release build keeps every protection enabled.

  • How do testers inspect traffic from a protected app?

    Differently per platform. From Android 7.0, a network security configuration can trust user-installed certificate authorities only in debuggable builds, so a proxy works in testing and the release build is unaffected. iOS has no direct equivalent: testers trust a proxy profile on a dedicated test device, and pinning needs its own test-build exception.

  • Why run resilience checks twice?

    Because resilience items are the ones a scoped test build changes. Run them once on the test build to confirm the exceptions behave, then again on the release build to confirm every protection responds as configured, covering repackaging, hooking, debugging, emulator, root and jailbreak checks.

  • When is a penetration test result finished?

    When someone else can reproduce it. Each checklist item records the expected event, the user journey it was tested in and the diagnostic result, such as logs or traffic captures. Clean-device acceptance also belongs in release criteria: install, update and first launch on a clean device must raise no protection event.

Keep reading

Discuss your protected app testing requirements

Tell us your platforms, framework and how your testers work today: devices, emulators or a cloud lab. We never ask for binaries, signing keys, credentials or production data.

Or request a technical demo