This playbook establishes a rigorous, comprehensive security reference for mobile application development. It addresses the 17 core mobile security domains required to protect user data, prevent unauthorized access, and ensure compliance with platform policies and industry standards (e.g., OWASP MASVS).
Each section provides a high-level review of the security domain, platform-specific mechanics (iOS and Android), concrete secure implementation recommendations, and rejection/compliance risks.
Sensitive information (session tokens, passwords, personal data, API keys, private cryptographic keys) must never be stored in plaintext. Standard application sandbox structures—such as UserDefaults on iOS or standard SharedPreferences on Android—write content directly to XML or plist files in unencrypted formats, allowing easy retrieval on jailbroken/rooted devices, or via backup analysis.
UserDefaults, CoreData (without custom encryption), plain SQLite, or standard application documents/cache directories.Data.WritingOptions.completeFileProtection to encrypt the file on disk, which remains encrypted when the device is locked.SharedPreferences, standard internal/external storage files, or standard SQLite databases.The iOS Keychain provides a secure, hardware-accelerated enclave to store small, highly sensitive configuration and credential items. The items are encrypted and managed directly by the operating system, isolated from other applications.
kSecAttrAccessible) when creating or updating Keychain items:
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: Highly Recommended for background tasks or push notification handlers. The data is available after the user unlocks the device once, and is not restored to another physical device via backups.kSecAttrAccessibleWhenUnlockedThisDeviceOnly: Use for foreground-only apps. The data is accessible only when the device is unlocked and is not backed up.kSecAttrAccessibleAlways (deprecated) and non-ThisDeviceOnly classes if you want to prevent migration of the credentials to other physical devices during backups.kSecAttrAccessGroup) strictly to verified app suites from the same team using Keychain Sharing entitlements.kSecAttrSynchronizable attribute is set to false (default) unless explicit iCloud Keychain synchronization is required.The Android Keystore system lets developers store cryptographic keys in a hardware-backed container (such as the Trusted Execution Environment (TEE) or StrongBox Security Chip), making them extremely difficult to extract from the device even if the operating system is compromised or rooted.
KeyInfo.isInsideSecureHardware() after key generation. On devices supporting Android 9 (API 28) or higher, use setIsStrongBoxBacked(true) to enforce hardware protection using a dedicated HSM chip.val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
keyGenerator.init(
KeyGenParameterSpec.Builder("MySecureKeyAlias", KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true) // require passcode/biometrics to use the key
.build()
)
Biometric authentication (FaceID, TouchID, Android BiometricPrompt) must verify user identity safely. A critical mistake is treating biometrics as an offline UI lock that merely flips a boolean flag (e.g., isLoggedIn = true) upon success. Attackers can bypass boolean returns using runtime instrumentation (e.g., Frida hooks on LAContext or BiometricPrompt).
SecAccessControl using .biometryAny or .biometryCurrentSet.LAContext.evaluatePolicy checks for session authorization unless paired with severe security constraints.BiometricPrompt.CryptoObject (wrapping a Cipher, Signature, or Mac associated with a Keystore key) to biometricPrompt.authenticate()..setUserAuthenticationRequired(true) and .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG).Certificate Pinning (or public-key pinning) mitigates Man-in-the-Middle (MITM) attacks by ensuring the client connects strictly to a server presenting a pre-defined cryptographic public key. This prevents traffic interception even if an attacker installs a malicious root certificate on the user’s device (e.g., via corporate proxies, malware, or user-approved configuration profiles).
Info.plist using the NSPinnedDomains key (supported natively since iOS 14). This operates safely inside the networking stack without manual delegation code.res/xml/network_security_config.xml) and specify the pins within <pin-set> elements. The system automatically enforces pinning for any HttpURLConnection or OkHttp client:
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.myproduction.com</domain>
<pin-set expiration="2026-12-31">
<pin digest="SHA-256">Base64EncodedSPKIHashHere=</pin>
<pin digest="SHA-256">Base64EncodedBackupSPKIHashHere=</pin>
</pin-set>
</domain-config>
</network-security-config>
A jailbroken iOS device bypasses critical platform sandboxing protections, allowing attackers to access private application data directories, hook runtime methods, and intercept secure communications.
/Applications/Cydia.app, /Applications/Sileo.app, /Library/MobileSubstrate/MobileSubstrate.dylib, /bin/bash, /usr/sbin/sshd, /private/var/lib/apt)./private/jailbreak.txt). Standard apps will fail; jailbroken apps with root access may succeed./Library/Ringtones or /Applications are symlinks, which jailbreak tools create to preserve disk space.MobileSubstrate, Frida).An Android device with root access grants administrative control, which bypasses critical security policies, memory isolation, and filesystem sandbox controls.
su, busybox, or root-management tools across standard paths (/system/bin/su, /system/xbin/su, /sbin/su, /system/sd/xbin/su).android.os.Build.TAGS for "test-keys", indicating a custom or rooted ROM build instead of a release-signed image.com.noshufou.android.su, eu.chainfire.supersu, com.topjohnwu.magisk).Misconfigured Transport Layer Security (TLS) leaves mobile applications vulnerable to credential sniffing and traffic modification. Cleartext traffic (HTTP) must be completely disabled in production.
NSAllowsArbitraryLoads to true in production Info.plist. If certain domains require HTTP exceptions, specify them explicitly under NSExceptionDomains with strict justifications.AndroidManifest.xml via android:usesCleartextTraffic="false".<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
By default, mobile operating systems include application sandboxed files in automated backups (iCloud backups on iOS, ADB / Google Drive backups on Android). This means sensitive data stored insecurely is automatically transferred to cloud drives or accessible via physical extraction.
.isExcludedFromBackup resource attribute to the file’s URL:
var url = URL(fileURLWithPath: path)
var values = URLResourceValues()
values.isExcludedFromBackup = true
try url.setResourceValues(values)
android:allowBackup="true" without defining tight filters. Backups allow extraction of private /data/data/your.package/ files using simple adb backup commands.<application android:allowBackup="false" ...>
android:dataExtractionRules (Android 12+) and android:fullBackupContent (Android 11-) in the manifest to target safe folders and explicitly exclude databases, shared preferences holding tokens, and credential files:
<!-- res/xml/data_extraction_rules.xml -->
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="auth_tokens.xml"/>
<exclude domain="database" path="secure_db.db"/>
</cloud-backup>
<device-to-device-backup>
<exclude domain="sharedpref" path="auth_tokens.xml"/>
<exclude domain="database" path="secure_db.db"/>
</device-to-device-backup>
</data-extraction-rules>
Any activity declared in AndroidManifest.xml with android:exported="true" can be launched by any other application running on the device. Unintentionally exported activities expose internal business logic, allow authorization bypass, and can cause tapjacking or state manipulation.
android:exported="false" for all internal activities.android:exported. If it lacks the tag, the app cannot be installed or submitted.<activity android:name=".MyExportedActivity"
android:exported="true"
android:permission="com.mycompany.myapp.PERMISSION_LAUNCH_ACTIVITY">
android:protectionLevel="signature", guaranteeing that only apps signed with your exact developer key can launch the activity.Intent filters define which implicit intents an application component can respond to. Registering intent filters automatically exports the component, potentially exposing it to untrusted third-party apps.
getCallingActivity() or getCallingPackage() to check if the caller matches a trusted package name and certificate.val explicitIntent = Intent(context, TargetActivity::class.java).apply {
putExtra("param", value)
}
startActivity(explicitIntent)
Deep links let external sources invoke internal routes within the app. Standard deep links using custom URL schemes (e.g., myapp://route) are inherently insecure, as any other application on the device can register the exact same scheme and hijack the communication, potentially stealing auth tokens or parameters.
Universal Links are iOS’s secure deep-linking mechanism. Instead of custom URL schemes, they use standard HTTPS links (e.g., https://mycompany.com/profile). Because domain validation is performed against the website’s configuration, Universal Links cannot be hijacked or duplicated by other applications.
https://yourdomain.com/.well-known/apple-app-site-association.
Content-Type: application/json and no redirects.{
"applinks": {
"details": [
{
"appIDs": ["TEAMID12345.com.mycompany.myapp"],
"components": [
{ "/": "/profile/*" }
]
}
]
}
}
applinks:yourdomain.com.Android App Links are standard HTTPS links that are configured to automatically launch the matching app directly, bypassing the platform “disambiguation dialog” by verifying domain ownership.
https://yourdomain.com/.well-known/assetlinks.json:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.mycompany.myapp",
"sha256_cert_fingerprints": ["AA:BB:CC:DD:EE:FF:11:22:33:44:55:66:77:88:99:00:11:22:33:44:55:66:77:88:99:00:11:22:33:44:55:66"]
}
}
]
android:autoVerify="true" to the intent filter within AndroidManifest.xml to instruct the operating system to verify the domain ownership on app installation:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="yourdomain.com" />
</intent-filter>
Mobile authentication flows must handle user credentials securely, prevent reverse engineering of backend secrets, and implement cryptographic challenges to prevent replay attacks.
ASWebAuthenticationSession on iOS and Custom Tabs on Android) for authentication screens. Avoid using embedded WKWebView or Android WebView components, which allow the hosting app to sniff or manipulate credentials and passwords entered by the user.Session tokens must be securely generated, properly scoped, periodically rotated, and gracefully destroyed when no longer in use.
Tokens (Access Tokens, Refresh Tokens, JWTs) are sensitive authorization credentials. If leaked, they allow full identity theft.
NSLog, print, Log.d).ThisDeviceOnly). Access tokens should be short-lived (e.g., 15 minutes), with refresh tokens rotated on each use.