Mobile Security
Mobile Security
Project: {{PROJECT_NAME}} Version: {{VERSION}} Date: {{DATE}} Author: {{AUTHOR}} Status: Draft | In Review | Approved Reviewers: {{REVIEWERS}}
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | {{DATE}} | {{AUTHOR}} | Initial draft |
1. Mobile Threat Model
| Threat Actor | Goal | Attack Vector | Likelihood | Impact | Mitigation |
|---|---|---|---|---|---|
| Malicious app on device | Steal auth tokens | Shared storage access | Medium | High | Secure storage (Keychain/Keystore) |
| Network interceptor (MITM) | Intercept API calls | Rogue WiFi, proxy | Medium | High | Certificate pinning |
| Reverse engineer | Extract API keys, business logic | APK/IPA decompilation | Medium | Medium | Code obfuscation, no hardcoded secrets |
| Stolen/lost device | Access user data | Physical access | High | High | Biometric lock, data encryption, remote wipe |
| Rooted/jailbroken device | Bypass controls | OS privilege escalation | Low | High | Root/jailbreak detection |
| Malicious insider | Access user data | Source code access | Low | High | Secrets in vault, no PII in logs |
Assets to protect:
- Auth tokens (access + refresh)
- User PII (name, email, payment info)
- API keys and secrets
- Cached sensitive data
2. Authentication
2.1 Biometric Authentication
Implementation: {{expo-local-authentication | react-native-biometrics}}
import * as LocalAuthentication from 'expo-local-authentication';
export async function authenticateWithBiometrics(): Promise<boolean> {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!hasHardware || !isEnrolled) {
return promptPINFallback();
}
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Verify your identity',
fallbackLabel: 'Use PIN',
cancelLabel: 'Cancel',
disableDeviceFallback: false,
});
return result.success;
}
Use cases for biometric auth:
- App unlock after backgrounding >
{{5 minutes}} - View sensitive data (payment info, full account number)
- Confirm high-value transactions (amount >
{{$100}}) - Change security settings
Fallback: PIN code → {{expo-secure-store}} stored hash (bcrypt, not reversible)
2.2 Session Management
| Property | Value |
|---|---|
| Access token lifetime | {{15 minutes}} |
| Refresh token lifetime | {{30 days}} |
| Refresh token rotation | Yes — single use, new token issued on use |
| Session timeout (background) | App lock after {{5 minutes}} inactive |
| Max concurrent sessions | {{3 devices}} |
| Force logout triggers | Password change, suspicious activity, admin revocation |
Token storage:
// CORRECT — encrypted secure storage
await SecureStore.setItemAsync('access_token', token, {
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
// WRONG — never use AsyncStorage or MMKV for tokens
// await AsyncStorage.setItem('access_token', token); // DO NOT USE
2.3 Token Storage (Secure Enclave)
| Platform | Mechanism | Access Level |
|---|---|---|
| iOS | Keychain Services | kSecAttrAccessibleWhenUnlockedThisDeviceOnly |
| Android | Android Keystore | BIOMETRIC_STRONG or DEVICE_CREDENTIAL |
Cannot be backed up: Both mechanisms are device-bound — tokens do not transfer to new devices.
3. Data Protection
3.1 Secure Storage Policy
| Data Type | Allowed Storage | Forbidden Storage |
|---|---|---|
| Access token | Keychain/Keystore | AsyncStorage, MMKV, SQLite |
| Refresh token | Keychain/Keystore | AsyncStorage, MMKV |
| Encryption key | Keychain/Keystore | Any other storage |
| User PII | SQLite (encrypted) | AsyncStorage |
| Non-sensitive prefs | AsyncStorage / MMKV | — |
3.2 Data Encryption at Rest
Database encryption: {{SQLCipher for SQLite | Realm Encryption}}
// SQLCipher initialization with key from Keystore
const encryptionKey = await SecureStore.getItemAsync('db_encryption_key');
const db = await SQLite.openDatabaseAsync('app.db', {
key: encryptionKey,
});
Key generation: On first launch, generate 256-bit random key, store in Keychain/Keystore.
Encrypted fields (beyond DB encryption):
| Field | Encryption | Key Source |
|---|---|---|
| Payment card (last 4 only stored) | N/A — tokenized | Stripe/Braintree |
| SSN / national ID | AES-256-GCM | Keychain/Keystore |
| Private messages | End-to-end (Signal protocol) | Derived keys |
3.3 Sensitive Data in Memory
| Rule | Implementation |
|---|---|
| Clear passwords after use | Overwrite string variable, trigger GC |
| No sensitive data in logs | Custom logger strips PII fields |
| No sensitive data in crash reports | Sentry beforeSend hook scrubs payload |
| No sensitive data in analytics | Map user ID → hashed ID before sending |
3.4 Screenshot Prevention
// iOS — prevent screenshots programmatically
// (Note: React Native does NOT expose this API — native module required)
// Android — prevent screenshots and screen recording
import { Platform } from 'react-native';
import { preventScreenCapture, allowScreenCapture } from 'expo-screen-capture';
useEffect(() => {
if (Platform.OS === 'android') {
preventScreenCapture();
}
return () => allowScreenCapture();
}, []);
Screens requiring screenshot prevention:
- Payment / card details screen
- Account balance screen
- Personal document viewer
-
{{OTHER_SENSITIVE_SCREEN}}
3.5 Clipboard Protection
- Password fields:
secureTextEntry={true}— disables clipboard by default on iOS - Sensitive data programmatically copied: clear clipboard after
{{60 seconds}} - Custom clipboard hook:
useSensitiveCopy(value, clearAfterMs: 60000)
4. Network Security
4.1 Certificate Pinning
Library: {{react-native-ssl-pinning | OkHttp certificate pinner (Android native) | URLSession (iOS native)}}
// Using react-native-ssl-pinning
const response = await fetch('https://api.domain.com/endpoint', {
method: 'POST',
pkPinning: true,
sslPinning: {
certs: ['api_cert_sha256_fingerprint'],
},
headers: { /* ... */ },
body: JSON.stringify(payload),
});
Pinned endpoints:
api.{{domain.com}}— primary APIauth.{{domain.com}}— authentication
Certificate rotation process:
- Pin BOTH current cert and backup cert simultaneously
- Deploy app update with new cert added
- After old cert expires, remove old cert from pins
- Test rotation in staging first
Handling pin failures:
- Log as security event to backend
- Show user-facing error: "Secure connection failed"
- Do NOT fall back to unpinned connection
4.2 SSL/TLS Configuration
| Requirement | Value |
|---|---|
| Minimum TLS version | TLS 1.2 |
| Preferred TLS version | TLS 1.3 |
| HTTP allowed | No (ATS enforced on iOS, enforce on Android) |
| Cipher suites | Forward-secrecy only (ECDHE, DHE) |
4.3 Network Request Encryption
- All requests over HTTPS (enforced via ATS + Android network security config)
- Sensitive payloads encrypted at application layer (in addition to TLS):
{{Yes/No}} - Request signing implemented (HMAC):
{{Yes/No}}
5. Application Security
5.1 Code Obfuscation
| Platform | Tool | Status |
|---|---|---|
| Android | ProGuard / R8 (enabled by default in release) | {{Done/TODO}} |
| iOS | Bitcode + Swift compiler optimizations | {{Done/TODO}} |
| JavaScript | Hermes bytecode compilation | {{Done/TODO}} |
| JavaScript (extra) | {{metro-react-native-babel-preset obfuscation}} |
{{Done/TODO}} |
ProGuard rules: android/app/proguard-rules.pro
TODO: Review ProGuard rules to ensure no over-stripping of reflection-dependent code.
5.2 Root / Jailbreak Detection
Library: {{expo-device | react-native-jail-monkey | custom}}
import JailMonkey from 'jail-monkey';
export function checkDeviceIntegrity(): SecurityResult {
const isJailbroken = JailMonkey.isJailBroken();
const isDebuggedBuild = JailMonkey.isDebuggedMode();
const onExternalStorage = JailMonkey.isOnExternalStorage(); // Android
return {
compromised: isJailbroken || isDebuggedBuild,
reason: isJailbroken ? 'jailbroken' : isDebuggedBuild ? 'debug' : 'clean',
};
}
Response to compromised device:
{{Warn user and allow use with reduced functionality | Block access entirely | Log silently}}- Justification:
{{Explain decision}}
5.3 Tamper Detection
- App bundle signature verification on launch
- Resource hash verification for critical assets
- Backend validates app version — block known compromised versions
5.4 Debug Detection
// Detect if app is running under debugger in production
if (__DEV__ === false && detectDebugger()) {
logSecurityEvent('debugger_attached');
terminateSession();
}
Production builds must have:
-
__DEV__mode disabled - Console.log stripped (Babel plugin:
transform-remove-console) - Flipper disabled
- Source maps NOT bundled with the app binary (upload to Sentry separately)
5.5 Reverse Engineering Prevention
| Measure | Implementation |
|---|---|
| No hardcoded secrets | All secrets fetched from vault at runtime or injected via CI |
| No plain API keys in bundle | Obfuscated + fetched from secure config endpoint |
| Sensitive logic on server | Business logic requiring security stays backend-side |
| Binary protection | ProGuard / R8 (Android), Bitcode stripping (iOS) |
TODO: Run MobSF static analysis before each major release and review findings.
6. OWASP Mobile Top 10 Checklist
| # | Risk | Status | Notes |
|---|---|---|---|
| M1 | Improper Credential Usage | {{Pass/Fail/N/A}} |
Tokens in Keychain, no hardcoded creds |
| M2 | Inadequate Supply Chain Security | {{Pass/Fail}} |
Dependency audit npm audit |
| M3 | Insecure Authentication/Authorization | {{Pass/Fail}} |
JWT validated server-side |
| M4 | Insufficient Input/Output Validation | {{Pass/Fail}} |
Zod validation on all inputs |
| M5 | Insecure Communication | {{Pass/Fail}} |
TLS + cert pinning |
| M6 | Inadequate Privacy Controls | {{Pass/Fail}} |
Data minimization, GDPR |
| M7 | Insufficient Binary Protections | {{Pass/Fail}} |
Obfuscation, no debug build in prod |
| M8 | Security Misconfiguration | {{Pass/Fail}} |
No dev configs in prod build |
| M9 | Insecure Data Storage | {{Pass/Fail}} |
Keychain/Keystore + encrypted DB |
| M10 | Insufficient Cryptography | {{Pass/Fail}} |
AES-256, SHA-256, no MD5/SHA-1 |
7. Security Testing Tools
| Tool | Type | When | Output |
|---|---|---|---|
| MobSF (Mobile Security Framework) | Static analysis | Pre-release | Risk report |
| Frida | Dynamic analysis | Penetration test | Runtime behavior |
| Objection | Runtime analysis | Penetration test | Bypass checks |
| Burp Suite | Network proxy | Penetration test | API vulnerabilities |
| OWASP ZAP | Automated scan | CI/CD | Vulnerability report |
npm audit / yarn audit |
Dependency scan | Every PR | CVE report |
Penetration test cadence: {{Annual | Before each major release | Quarterly}}
Last pentest date: {{DATE}}
Next pentest date: {{DATE}}
8. Compliance Requirements
| Requirement | Applicable | Notes |
|---|---|---|
| GDPR (EU users) | {{Yes/No}} |
User data deletion, export endpoints |
| CCPA (California users) | {{Yes/No}} |
Do Not Sell option |
| COPPA (under 13) | {{Yes/No}} |
Parental consent flow |
| PCI DSS (payment data) | {{Yes/No}} |
Use PCI-compliant SDK (Stripe/Braintree) — never store card data |
| App Store privacy label | Yes | App Store Connect privacy questionnaire |
| Play Store data safety | Yes | Google Play Console data safety form |
| HIPAA (health data) | {{Yes/No}} |
BAA with vendors, encryption at rest+transit |
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | |||
| Mobile Lead | |||
| Security Lead | |||
| Legal |
No comments to display
No comments to display