Comprehensive mobile security review covering Android MASVS (exported components, Intent injection, WebView, insecure storage, Keystore, network_security_config, logging, backup) and iOS MASVS (ATS bypass, URL scheme, Keychain, pasteboard, screenshot, CommonCrypto, jailbreak, insecure storage) plus shared mobile concerns (cert pinning, biometric, deep links, binary hardening, privacy). Trigger for ANY mobile code review (.kt, .java, .swift, .m files) or mobile platform changes.
Comprehensive security review for mobile applications covering OWASP MASVS controls: storage security, cryptography, authentication, network security, platform interaction, code quality, and resilience. Covers both Android and iOS platforms plus shared mobile security concerns.
This skill covers 21 mobile security classes organized by platform:
Android (8): Insecure storage, exported components, Intent injection, WebView security, insecure crypto, network security config, logging sensitive data, backup exposure
iOS (8): Insecure storage, ATS bypass, URL scheme hijacking, Keychain misuse, pasteboard leaks, screenshot exposure, insecure crypto, jailbreak detection bypass
Shared (5): Certificate pinning, biometric auth bypass, deep link hijacking, binary hardening, privacy/data collection
[MASVS-STORAGE]Red flags:
// ❌ UNSAFE
val prefs = getSharedPreferences("auth", Context.MODE_PRIVATE)
prefs.edit().putString("api_key", apiKey).apply() // Plaintext
[MASVS-PLATFORM]Red flags:
<!-- ❌ UNSAFE -->
<activity android:name=".AdminActivity" android:exported="true" />
<service android:name=".PaymentService" android:exported="true" />
[MASVS-PLATFORM]Red flags:
// ❌ UNSAFE
val url = intent.getStringExtra("url")
webView.loadUrl(url) // Attacker-controlled URL
[MASVS-PLATFORM]Red flags:
// ❌ UNSAFE
webView.settings.javaScriptEnabled = true
webView.settings.allowFileAccessFromFileURLs = true
webView.addJavascriptInterface(jsInterface, "Android")
webView.loadUrl(userProvidedUrl)
[MASVS-CRYPTO]Red flags:
// ❌ UNSAFE
val key = "hardcoded1234567"
val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
[MASVS-NETWORK]Red flags:
<!-- ❌ UNSAFE -->
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
[MASVS-STORAGE]Red flags:
// ❌ UNSAFE
Log.d("Auth", "Token: $authToken")
Log.i("User", "SSN: ${user.ssn}")
[MASVS-STORAGE]Red flags:
<!-- ❌ UNSAFE -->
<application android:allowBackup="true" ... >
[MASVS-STORAGE]Red flags:
// ❌ UNSAFE
UserDefaults.standard.set(apiToken, forKey: "token")
try? data.write(to: documentsURL.appendingPathComponent("secret.dat"))
[MASVS-NETWORK]Red flags:
<!-- ❌ UNSAFE -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
[MASVS-PLATFORM]Red flags:
// ❌ UNSAFE
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
webView.load(URLRequest(url: url)) // Unvalidated
return true
}
[MASVS-CRYPTO]Red flags:
// ❌ UNSAFE
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "token",
kSecValueData as String: token.data(using: .utf8)!,
kSecAttrAccessible as String: kSecAttrAccessibleAlways // Wrong!
]
[MASVS-STORAGE]Red flags:
// ❌ UNSAFE
UIPasteboard.general.string = user.creditCardNumber
[MASVS-STORAGE]Red flags:
// ❌ Missing: No applicationDidEnterBackground implementation to hide sensitive views
[MASVS-CRYPTO]Red flags:
// ❌ UNSAFE
let key = "hardcoded12345"
CCCrypt(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmDES), ...)
[MASVS-RESILIENCE]Red flags:
// ❌ WEAK
func isJailbroken() -> Bool {
return FileManager.default.fileExists(atPath: "/Applications/Cydia.app")
}
[MASVS-NETWORK]See also: security-supply-chain skill for detailed pinning guidance.
[MASVS-AUTH]Red flags (iOS):
// ❌ UNSAFE: Local-only
let context = LAContext()
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) {
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Auth") { success, _ in
if success {
self.showSecretData() // No backend verification!
}
}
}
[MASVS-PLATFORM][MASVS-RESILIENCE][MASVS-PRIVACY]# Search for insecure patterns
rg "MODE_WORLD_READABLE|getSharedPreferences.*MODE_PRIVATE|\.putString\(.*api|\.putString\(.*token"
rg "openOrCreateDatabase|SQLiteDatabase.openDatabase"
Check for EncryptedSharedPreferences usage:
// ✅ SAFE
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
# Check AndroidManifest.xml
grep -r "android:exported=\"true\"" AndroidManifest.xml
grep -r "<intent-filter>" AndroidManifest.xml # Implicitly exported
# Check Info.plist
grep -A 10 "NSAppTransportSecurity" */Info.plist
// ✅ SAFE: Backend verification
let context = LAContext()
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Auth") { success, error in
if success {
// Get short-lived token from backend
self.apiClient.getBiometricToken { token in
// Use token for sensitive operation
}
}
}
// ✅ SAFE
val uri = intent.data
if (uri?.host != "myapp.com" || !uri.pathSegments.all { it.matches(Regex("[a-zA-Z0-9-]+")) }) {
finish()
return
}