Mobile Security
Mobile Security
Project: Drop — Fintech Payment App Version: 0.1.0 Date: 2026-02-23 Author: John (AI Director, ALAI) Status: Draft Reviewers: Alem Bašić (CEO)
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | 2026-02-23 | John | Initial draft — current state analysis + Phase 2 security roadmap |
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 Bearer token | Shared AsyncStorage access | Medium | Critical | Phase 2: migrate token to expo-secure-store |
| Network interceptor (MITM) | Intercept payment API calls | Rogue WiFi, proxy | Medium | Critical | TLS 1.3 enforced; certificate pinning Phase 2 |
| Reverse engineer | Extract API keys, business logic | APK/IPA decompilation | Medium | Medium | Hermes bytecode; no hardcoded secrets |
| Stolen/lost device | Initiate payments as victim | Physical access + stored token | High | Critical | Phase 2: biometric lock, token binding |
| Rooted/jailbroken device | Bypass security controls | OS privilege escalation | Low | High | Root/jailbreak detection Phase 2 |
| Session hijacking | Replay stolen token | Network capture | Low | Critical | Token expiry (7 days); HTTPS enforced |
| Social engineering | Steal BankID credentials | Phishing | Medium | Critical | User education; BankID handles its own auth |
Assets to protect (priority order):
- Bearer token (allows payment initiation from user's bank account)
- User PII (name, email, phone — BankID-verified identity)
- Transaction history (financial data)
- Exchange rate API access
- App integrity (prevent tampered builds)
2. Authentication
2.1 Biometric Authentication
Current state (Phase 1): Not implemented. Login is email + password only.
Phase 2 implementation:
// 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: 'Bekreft identiteten din',
fallbackLabel: 'Bruk PIN',
cancelLabel: 'Avbryt',
disableDeviceFallback: false,
});
return result.success;
}
Planned biometric use cases (Phase 2):
- App unlock after backgrounding > 5 minutes
- Confirm high-value remittance transactions (amount > 5000 NOK)
- Change security settings
- View full transaction history
Fallback: 4-digit PIN (same as web registration flow) → hash stored in expo-secure-store (bcrypt, not reversible)
2.2 Session Management
| Property | Current (Phase 1) | Phase 2 Target |
|---|---|---|
| Access token lifetime | 7 days (Bearer) | 15 minutes (access) + 7 days (refresh) |
| Refresh token | Not implemented | Rotate on each use |
| Session timeout | Never (token-based) | App lock after 5 min background |
| Max concurrent sessions | Not limited | 3 devices max |
| Force logout triggers | Manual logout only | Password change, suspicious activity, admin revoke |
Token storage (current Phase 1):
// 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 — DO NOT USE FOR TOKENS
// await AsyncStorage.setItem('access_token', token); // Unencrypted!
2.3 BankID Authentication
BankID is Drop's primary strong authentication mechanism. The BankID flow is handled by the BankID OIDC provider — Drop does 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 redirects to drop://auth/callback?code=&state=
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:
- Norwegian national ID system — eID Level 3 (substantial assurance)
- All authentication happens in BankID's secure environment
- Drop never sees BankID PIN or biometric data
- BankID tokens are single-use and time-limited
3. Data Protection
3.1 Secure Storage Policy
| Data Type | Phase 1 Storage | Phase 2 Storage | Forbidden |
|---|---|---|---|
| Bearer token | AsyncStorage (unencrypted) | expo-secure-store (Keychain/Keystore) | Hardcoded |
| Refresh token | Not implemented | expo-secure-store | AsyncStorage |
| User PII | Not cached locally | SQLite (encrypted with SQLCipher) | AsyncStorage |
| Non-sensitive prefs | AsyncStorage | AsyncStorage | — |
| Biometric key hash | Not implemented | expo-secure-store | Any other storage |
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:
// Phase 2 — lib/database.ts
import * as SQLite from 'expo-sqlite';
import * as SecureStore from 'expo-secure-store';
async function openDatabase() {
// Generate encryption key on first launch
let dbKey = await SecureStore.getItemAsync('db_encryption_key');
if (!dbKey) {
dbKey = generateRandomKey(256); // 256-bit random key
await SecureStore.setItemAsync('db_encryption_key', dbKey);
}
return await SQLite.openDatabaseAsync('drop.db', { key: dbKey });
}
3.3 Sensitive Data in Memory
| Rule | Phase 1 Status | Phase 2 Implementation |
|---|---|---|
| Clear passwords after use | Partially (useState cleanup) | Overwrite variable, trigger GC |
| No PII in logs | Implemented — no console.log(user) in production paths |
Custom logger strips PII fields |
| No PII in crash reports | TBD — no crash reporting in Phase 1 | Sentry beforeSend hook scrubs payload |
| No PII in analytics | TBD — no analytics in Phase 1 | Hash user IDs |
3.4 Screenshot Prevention
Drop displays financial data (balance, transaction amounts). Screenshots should be prevented on sensitive screens.
// Phase 2 — sensitive screen protection
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(() => {
preventScreenCapture();
return () => allowScreenCapture();
}, []);
Note: iOS screenshot prevention via expo-screen-capture shows a blank screen in the app switcher but cannot prevent all screenshots. Android FLAG_SECURE prevents screenshots and screen recording.
3.5 Clipboard Protection
- Password fields:
secureTextEntry={true}in React Native TextInput — disables clipboard by default on iOS - Transaction amounts copied to clipboard: clear after 60 seconds (Phase 2)
- Account numbers: never copied to clipboard by app — shown as masked
4. Network Security
4.1 Certificate Pinning
Current (Phase 1): Not implemented. Standard HTTPS/TLS only.
Phase 2 implementation:
// Phase 2 — requires 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', {
method: 'GET',
// Certificate pinning configuration here
headers: { Authorization: `Bearer ${token}` },
});
Certificate rotation process:
- Pin both 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 production
4.2 SSL/TLS Configuration
| Requirement | Value |
|---|---|
| Minimum TLS version | TLS 1.2 |
| Preferred TLS version | TLS 1.3 |
| HTTP allowed | No (Vercel enforces HTTPS) |
| Cipher suites | TLS 1.3 default (forward-secrecy) |
iOS App Transport Security (ATS):
- No
NSAllowsArbitraryLoads: true - All API calls go to
https://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 Security
- All requests over HTTPS (enforced by Vercel)
- Request signing (HMAC) — not implemented (Phase 2)
- Application-layer encryption for payment payloads — TBD (Phase 2)
5. Application Security
5.1 Code Obfuscation
| Platform | Tool | Status |
|---|---|---|
| Android | ProGuard/R8 via Expo release build | Done (Expo default) |
| iOS | Swift compiler optimizations | Done (Expo default) |
| JavaScript | Hermes bytecode compilation | Done (Expo default) |
Hermes: Expo SDK 54 uses Hermes by default. JavaScript is compiled to bytecode, making reverse engineering significantly harder than raw JS.
5.2 Root / Jailbreak Detection
Current (Phase 1): Not implemented.
Phase 2 implementation:
// Phase 2 — lib/integrity.ts
import * as Device from 'expo-device';
export function checkDeviceIntegrity(): { compromised: boolean; reason: string } {
// expo-device provides basic device info
// For more robust detection: react-native-device-info or jail-monkey
const isEmulator = !Device.isDevice;
return {
compromised: isEmulator && !__DEV__,
reason: isEmulator ? 'emulator_in_production' : 'clean',
};
}
Response to compromised device (Phase 2):
- Warn user: "Drop er ikke tilgjengelig på enheter med modifisert programvare"
- Allow use with warning (not blocking in Phase 2) — higher blocking for payment flows
- Log security event to backend
5.3 Debug Detection
Production builds must have:
-
__DEV__mode disabled (Expo release build) -
console.logstripped (Expo/Metro default in release builds) - Flipper disabled (Expo default in release builds)
- Source maps NOT bundled with the app — upload to Sentry separately (Phase 2)
5.4 Reverse Engineering Prevention
| Measure | Phase 1 | Phase 2 |
|---|---|---|
| No hardcoded secrets | Done — API URL only, no keys | API keys fetched at runtime from secure config |
| No plain API keys in bundle | Done | Done |
| Sensitive logic on server | Done — Drop is pass-through; payments initiated server-side | Done |
| Binary protection | Hermes bytecode (done) | ProGuard rules review |
6. OWASP Mobile Top 10 Checklist
| # | Risk | Phase 1 Status | Notes |
|---|---|---|---|
| M1 | Improper Credential Usage | Partial | Bearer token in AsyncStorage (unencrypted) — Phase 2 fix |
| M2 | Inadequate Supply Chain Security | Unknown | npm audit not run in CI yet |
| M3 | Insecure Authentication/Authorization | Pass | BankID + JWT validated server-side |
| M4 | Insufficient Input/Output Validation | Pass | React Native TextInput handles basic validation; XSS not applicable to React Native |
| M5 | Insecure Communication | Pass | HTTPS enforced; cert pinning Phase 2 |
| M6 | Inadequate Privacy Controls | Partial | Data minimization done; GDPR consent in web app — mobile privacy notice TBD |
| M7 | Insufficient Binary Protections | Pass | Hermes bytecode; Expo release build |
| M8 | Security Misconfiguration | Pass | No dev configs in prod build; __DEV__ disabled |
| M9 | Insecure Data Storage | Fail | Bearer token in unencrypted AsyncStorage — fix before production |
| M10 | Insufficient Cryptography | Pass | TLS 1.3; no MD5/SHA-1 usage |
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 |
npm audit / pnpm audit |
Dependency scan | Every PR | CVE report |
| Expo Security Checks | Build-time | EAS Build | Security warnings |
| Burp Suite | Network proxy | Penetration test | API vulnerabilities |
| Frida | Dynamic analysis | Penetration test | Runtime behavior |
| OWASP ZAP | Automated API scan | CI/CD | Vulnerability report |
Penetration test cadence: Before v1.0 production launch (pre-launch), then annually. Last pentest date: Not yet conducted. Next pentest date: Before production launch.
8. Compliance Requirements
| Requirement | Applicable | Status | Notes |
|---|---|---|---|
| GDPR (EU/EEA users) | Yes | Partial | Privacy policy at /privacy; data deletion via support; consent management in web app |
| PSD2 (Payment Services Directive 2) | Yes | Pass | PISP/AISP model; Open Banking; pre-payment disclosure implemented |
| Finanstilsynet (Norwegian FSA) | Yes | In progress | Regulatory compliance ongoing |
| Hvitvaskingsloven (AML) | Yes | Pass | 5-year data retention; KYC via BankID |
| CCPA | No | N/A | Norwegian market only — no California users targeted |
| COPPA | No | N/A | 18+ minimum age (BankID requirement) |
| PCI DSS | No | N/A | Drop never stores card data — pass-through model |
| App Store Privacy Label | Yes | TODO | Must complete before submission |
| Play Store Data Safety | Yes | TODO | Must complete before submission |
| HIPAA | No | N/A | No health data |
9. Security Roadmap
Phase 1 (Current — MVP)
- HTTPS/TLS enforced
- BankID authentication via expo-web-browser
- Hermes bytecode (basic obfuscation)
- CRITICAL: Migrate Bearer token to expo-secure-store (before production)
Phase 2 (Pre-Launch)
- expo-secure-store for all tokens
- Certificate pinning for API endpoints
- Biometric authentication (Face ID / Touch ID)
- Screenshot prevention on financial screens
- Root/jailbreak detection with user warning
- Mobile penetration test (MobSF + Burp Suite)
- Source maps configured (Sentry, not bundled)
Phase 3 (Post-Launch)
- App attestation (Play Integrity API / App Attest)
- Transaction anomaly detection
- Session device binding
- Refresh token rotation
- Self-service account deletion (GDPR)
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | John (AI Director) | 2026-02-23 | |
| Mobile Lead | |||
| Security Lead | |||
| Legal |