Mobile Security
Mobile Security
Project:
Drop — Fintech Payment App{{PROJECT_NAME}} Version:0.1.0{{VERSION}} Date:2026-02-23{{DATE}} Author:John (AI Director, ALAI){{AUTHOR}} Status: Draft | In Review | Approved Reviewers:Alem Bašić (CEO){{REVIEWERS}}
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | Initial draft |
1. Mobile Threat Model
Context: Drop is a PSD2-regulated payment app. It handles real money movements from users' bank accounts. Drop never stores funds, but it stores authentication tokens that could initiate payments if compromised.
| Threat Actor | Goal | Attack Vector | Likelihood | Impact | Mitigation |
|---|---|---|---|---|---|
| Malicious app on device | Steal |
Shared |
Medium | ||
| Network interceptor (MITM) | Intercept |
Rogue WiFi, proxy | Medium | ||
| Reverse engineer | Extract API keys, business logic | APK/IPA decompilation | Medium | Medium | |
| Stolen/lost device | Physical access |
High | |||
| Rooted/jailbroken device | Bypass |
OS privilege escalation | Low | High | Root/jailbreak detection |
| Low | |||||
Assets to protect (priority order):protect:
BearerAuthtokentokens (allowsaccesspayment+initiation from user's bank account)refresh)- User PII (name, email,
phonepayment— BankID-verified identity)info) TransactionAPIhistorykeys(financialanddata)secretsExchangeCachedratesensitiveAPI accessApp integrity (prevent tampered builds)data
2. Authentication
2.1 Biometric Authentication
Current state (Phase 1): Not implemented. Login is email + password only.
PhaseImplementation: 2{{expo-local-authentication implementation:| react-native-biometrics}}
// Phase 2 — lib/biometrics.ts
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: 'BekreftVerify identitetenyour din'identity',
fallbackLabel: 'BrukUse PIN',
cancelLabel: 'Avbryt'Cancel',
disableDeviceFallback: false,
});
return result.success;
}
PlannedUse cases for biometric use cases (Phase 2):auth:
- App unlock after backgrounding >
{{5minutesminutes}} - View sensitive data (payment info, full account number)
- Confirm high-value
remittancetransactions (amount >5000 NOK){{$100}}) - Change security settings
View full transaction history
Fallback: 4-digit PIN (same as web registration flow)code → hash{{expo-secure-store}} stored in hash (bcrypt, not reversible)expo-secure-store
2.2 Session Management
| Property | ||
|---|---|---|
| Access token lifetime | {{15 |
|
| Refresh token lifetime | {{30 |
|
| Refresh token rotation | ||
| Session timeout | App lock after {{5 |
|
| Max concurrent sessions | {{3 |
|
| Force logout triggers | Password change, suspicious activity, admin |
Token storage (current Phase 1):storage:
// CURRENT — lib/api.js — in-memory + AsyncStorage
// WARNING: AsyncStorage is not encrypted — acceptable for MVP, not for production
let token = null; // Module-level in-memory
await AsyncStorage.setItem('auth_token', token); // Persisted (unencrypted)
Token storage (Phase 2 target):
// PHASE 2 — lib/auth.ts
import * as SecureStore from 'expo-secure-store';
// CORRECT — encrypted secure storage
await SecureStore.setItemAsync('access_token', token, {
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
// WRONG — DOnever NOTuse USEAsyncStorage FORor TOKENSMMKV for tokens
// await AsyncStorage.setItem('access_token', token); // Unencrypted!DO NOT USE
2.3 BankIDToken AuthenticationStorage (Secure Enclave)
| Platform | Mechanism | Access Level |
|---|---|---|
| iOS | Keychain Services | kSecAttrAccessibleWhenUnlockedThisDeviceOnly |
| Android | Android Keystore | BIOMETRIC_STRONG or DEVICE_CREDENTIAL |
BankIDCannot isbe Drop'sbacked primaryup: strongBoth authenticationmechanisms mechanism.are The BankID flow is handled by the BankID OIDC providerdevice-bound — Droptokens doesdo not see or store BankID credentials.
Mobile BankID flow (ADR-011):
1. GET /v1/auth/bankid/initiate?platform=mobile → { redirectUrl, state }
2. Open BankID in expo-web-browser (secure, isolated browser)
3. BankID redirectstransfer to drop://auth/callback?code=&state=new 4. expo-linking catches the deep link
5. POST /v1/auth/bankid/callback → Bearer token (7-day)
6. Token stored (AsyncStorage Phase 1, expo-secure-store Phase 2)
Security properties of BankID:devices.
Norwegian national ID system — eID Level 3 (substantial assurance)All authentication happens in BankID's secure environmentDrop never sees BankID PIN or biometric dataBankID tokens are single-use and time-limited
3. Data Protection
3.1 Secure Storage Policy
| Data Type | Forbidden Storage | ||
|---|---|---|---|
| Refresh token | |||
| Encryption key | Any other storage | ||
| User PII | SQLite ( |
AsyncStorage | |
| Non-sensitive prefs | AsyncStorage | — | |
Phase 1 risk: Bearer token stored in AsyncStorage (unencrypted) is the primary security gap. Mitigation: 7-day token expiry limits exposure window. Production apps should implement Phase 2 before launch.
3.2 Data Encryption at Rest
Phase 1: No local database. Auth token in AsyncStorage (unencrypted).
Phase 2 — Database encryption: {{SQLCipher for SQLite | Realm Encryption}}
// PhaseSQLCipher 2initialization —with lib/database.ts
import * as SQLitekey from 'expo-sqlite';Keystore
importconst * as SecureStore from 'expo-secure-store';
async function openDatabase() {
// Generate encryption key on first launch
let dbKeyencryptionKey = await SecureStore.getItemAsync('db_encryption_key');
ifconst (!dbKey) {
dbKeydb = generateRandomKey(256); // 256-bit random key
await SecureStore.setItemAsync('db_encryption_key', dbKey);
}
return await SQLite.openDatabaseAsync('drop.app.db', {
key: dbKeyencryptionKey,
});
}
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 | ||
|---|---|---|
| Clear passwords after use | ||
| No | |
Custom logger strips PII fields |
| No | Sentry beforeSend hook scrubs payload |
|
| No |
3.4 Screenshot Prevention
Drop displays financial data (balance, transaction amounts). Screenshots should be prevented on sensitive screens.
// Phase 2iOS — sensitiveprevent screenshots programmatically
// (Note: React Native does NOT expose this API — native module required)
// Android — prevent screenshots and screen protectionrecording
import { Platform } from 'react-native';
import { preventScreenCapture, allowScreenCapture } from 'expo-screen-capture';
// Screens requiring screenshot prevention:
// - Dashboard (shows bank balance)
// - Send Money (shows account balance, transaction amounts)
// - Transaction History (financial data)
useEffect(() => {
if (Platform.OS === 'android') {
preventScreenCapture();
}
return () => allowScreenCapture();
}, []);
Note:Screens iOSrequiring screenshot preventionprevention:
- Payment / card details screen
- Account balance screen
- Personal document viewer
-
expo-screen-capture{{OTHER_SENSITIVE_SCREEN}}
3.5 Clipboard Protection
- Password fields:
secureTextEntry={true}in React Native TextInput— disables clipboard by default on iOS -
TransactionSensitiveamountsdatacopiedprogrammaticallyto clipboard:copied: clear clipboard after{{60seconds (Phase 2)seconds}} -
Account numbers: never copied toCustom clipboardbyhook:appuseSensitiveCopy(value,—clearAfterMs:shown as masked60000)
4. Network Security
4.1 Certificate Pinning
Current (Phase 1): Not implemented. Standard HTTPS/TLS only.
PhaseLibrary: 2{{react-native-ssl-pinning implementation:| OkHttp certificate pinner (Android native) | URLSession (iOS native)}}
// Phase 2 — requiresUsing react-native-ssl-pinning or OkHttp pinner
// Pinned endpoints:
// - drop-app.vercel.app (production API)
// - BankID OIDC endpoints
const response = await fetch('https://drop-app.vercel.app/api/transactions'api.domain.com/endpoint', {
method: 'GET'POST',
//pkPinning: Certificatetrue,
pinningsslPinning: configuration{
herecerts: ['api_cert_sha256_fingerprint'],
},
headers: { Authorization:/* `Bearer... ${token}`*/ },
body: JSON.stringify(payload),
});
Pinned endpoints:
api.{{domain.com}}— primary APIauth.{{domain.com}}— authentication
Certificate rotation process:
- Pin
bothBOTH current cert and backup cert simultaneously - Deploy app update with new cert added
(OTA via EAS Update) - After old cert expires, remove old cert from pins
- Test rotation in staging
before productionfirst
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 ( |
| Cipher suites |
iOS App Transport Security (ATS):
NoNSAllowsArbitraryLoads: trueAll API calls go tohttps://drop-app.vercel.app(HTTPS enforced)
Android Network Security Config:
<!-- android/app/src/main/res/xml/network_security_config.xml — Phase 2 -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">getdrop.no</domain>
<domain includeSubdomains="true">drop-app.vercel.app</domain>
</domain-config>
</network-security-config>
4.3 Network Request SecurityEncryption
- All requests over HTTPS (enforced
byviaVercel)ATS + Android network security config) - Sensitive payloads encrypted at application layer (in addition to TLS):
{{Yes/No}} - Request signing
(HMAC) — notimplemented (PhaseHMAC):2) Application-layer encryption for payment payloads — TBD (Phase 2){{Yes/No}}
5. Application Security
5.1 Code Obfuscation
| Platform | Tool | Status |
|---|---|---|
| Android | {{Done/TODO}} |
|
| iOS | Bitcode + Swift compiler optimizations | {{Done/TODO}} |
| JavaScript | Hermes bytecode compilation | {{Done/TODO}} |
| JavaScript ( |
{{metro-react-native-babel-preset obfuscation}} |
{{Done/TODO}} |
Hermes:ProGuard rules: Expoandroid/app/proguard-rules.pro
SDKTODO: 54Review usesProGuard Hermes by default. JavaScript is compiledrules to bytecode,ensure makingno reverseover-stripping engineeringof significantlyreflection-dependent hardercode.
5.2 Root / Jailbreak Detection
Current (Phase 1): Not implemented.
PhaseLibrary: 2{{expo-device implementation:| react-native-jail-monkey | custom}}
// Phase 2 — lib/integrity.ts
import * as DeviceJailMonkey from 'expo-device'jail-monkey';
export function checkDeviceIntegrity(): SecurityResult {
compromised:const boolean;isJailbroken reason:= stringJailMonkey.isJailBroken();
}const {isDebuggedBuild = JailMonkey.isDebuggedMode();
const onExternalStorage = JailMonkey.isOnExternalStorage(); // expo-device provides basic device info
// For more robust detection: react-native-device-info or jail-monkey
const isEmulator = !Device.isDevice;Android
return {
compromised: isEmulatorisJailbroken &&|| !__DEV__,isDebuggedBuild,
reason: isEmulatorisJailbroken ? 'emulator_in_production'jailbroken' : isDebuggedBuild ? 'debug' : 'clean',
};
}
Response to compromised device (Phase 2):device:
{{Warnuser:user"Dropander ikke tilgjengelig på enheter med modifisert programvare"Allowallow use withwarningreduced(notfunctionalityblocking|inBlockPhaseaccess2)entirely—|higherLogblocking for payment flowssilently}}LogJustification:security{{Explainevent to backenddecision}}
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(Expo release build) -
stripped (console.Console.logExpo/MetroBabeldefaultplugin:in release builds)transform-remove-console) - Flipper disabled
(Expo default in release builds) - Source maps NOT bundled with the app
—binary (upload to Sentryseparately (Phase 2)separately)
5.45 Reverse Engineering Prevention
| Measure | ||
|---|---|---|
| No hardcoded secrets | ||
| No plain API keys in bundle | ||
| Sensitive logic on server | ||
| Binary protection |
TODO: Run MobSF static analysis before each major release and review findings.
6. OWASP Mobile Top 10 Checklist
| # | Risk | Notes | |
|---|---|---|---|
| M1 | Improper Credential Usage | {{Pass/Fail/N/A}} |
|
| M2 | Inadequate Supply Chain Security | {{Pass/Fail}} |
Dependency audit npm audit |
| M3 | Insecure Authentication/Authorization | {{Pass/Fail}} |
|
| M4 | Insufficient Input/Output Validation | {{Pass/Fail}} |
|
| M5 | Insecure Communication | {{Pass/Fail}} |
|
| M6 | Inadequate Privacy Controls | {{Pass/Fail}} |
Data |
| M7 | Insufficient Binary Protections | {{Pass/Fail}} |
|
| M8 | Security Misconfiguration | {{Pass/Fail}} |
No dev configs in prod |
| M9 | Insecure Data Storage | {{Pass/Fail}} |
|
| M10 | Insufficient Cryptography | {{Pass/Fail}} |
Critical action before production: M9 (Insecure Data Storage) — migrate Bearer token from AsyncStorage to expo-secure-store. This is the single most important security fix before v1.0 production launch.
7. Security Testing Tools
| Tool | Type | When | Output |
|---|---|---|---|
| MobSF (Mobile Security Framework) | Static analysis | Pre-release | Risk report |
Frida |
|||
| Burp Suite | Network proxy | Penetration test | API vulnerabilities |
| OWASP ZAP | Automated |
CI/CD | Vulnerability report |
npm audit / yarn audit |
Dependency scan | Every PR | CVE report |
Penetration test cadence: {{Annual | Before
Last pentest date: v1.0each productionmajor launchrelease (pre-launch),| then annually.Quarterly}}Not yet conducted.{{DATE}}
Next pentest date: Before production launch.{{DATE}}
8. Compliance Requirements
| Requirement | Applicable | Notes | |
|---|---|---|---|
| GDPR ( |
{{Yes/No}} |
| |
| CCPA (California users) | {{Yes/No}} |
||
| COPPA (under 13) | {{Yes/No}} |
||
| PCI DSS (payment data) | {{Yes/No}} |
||
| App Store |
Yes | ||
| Play Store |
Yes | ||
| HIPAA (health data) | {{Yes/No}} |
9. Security Roadmap
Phase 1 (Current — MVP)
HTTPS/TLS enforcedBankID authentication via expo-web-browserHermes bytecode (basic obfuscation)CRITICAL: Migrate Bearer token to expo-secure-store (before production)
Phase 2 (Pre-Launch)
expo-secure-store for all tokensCertificate pinning for API endpointsBiometric authentication (Face ID / Touch ID)Screenshot prevention on financial screensRoot/jailbreak detection with user warningMobile penetration test (MobSF + Burp Suite)Source maps configured (Sentry, not bundled)
Phase 3 (Post-Launch)
App attestation (Play Integrity API / App Attest)Transaction anomaly detectionSession device bindingRefresh token rotationSelf-service account deletion (GDPR)
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | |||
| Mobile Lead | |||
| Security Lead | |||
| Legal |