Sensitive data is encrypted at rest using platform-secure storage
Why it matters
AsyncStorage in React Native is unencrypted plaintext on every platform — its contents are readable from a jailbroken iOS device or from any app on Android versions below 4.4. Storing auth tokens, JWTs, or user PII in AsyncStorage means a single compromised device exposes session credentials that can be replayed to impersonate the user with full account access. OWASP 2021 A02 (Cryptographic Failures), CWE-312 (Cleartext Storage of Sensitive Information), HIPAA §164.312(a)(2)(iv), and NIST SP 800-218 PW.4 all require sensitive data at rest to be encrypted using platform-provided mechanisms (Keychain on iOS, Keystore on Android).
Severity rationale
High because auth tokens in AsyncStorage are accessible to any process on a compromised device, enabling session hijacking and full account takeover without a network attack.
Remediation
Replace AsyncStorage for sensitive data with expo-secure-store, which uses Keychain on iOS and EncryptedSharedPreferences on Android:
import * as SecureStore from 'expo-secure-store';
// Store
await SecureStore.setItemAsync('authToken', token);
// Retrieve
const token = await SecureStore.getItemAsync('authToken');
For non-Expo React Native, use react-native-keychain:
import * as Keychain from 'react-native-keychain';
await Keychain.setGenericPassword('token', jwtValue);
Search for every AsyncStorage.setItem call in src/ and audit what is being stored. Move tokens and PII to secure storage; leave only non-sensitive preferences (theme, language) in AsyncStorage.
Detection
- ID:
sensitive-data-encryption - Severity:
high - What to look for: Count all relevant instances and enumerate each. Look for sensitive data stored in unencrypted storage. The key risk:
AsyncStorage(React Native) is plaintext and unencrypted — any data stored there is readable from a jailbroken/rooted device and accessible to other apps on older Android versions. Search forAsyncStorage.setItemcalls that store: auth tokens, session tokens, refresh tokens, JWT, API keys, user PII (name, email, phone, address), health data, financial data, or any object stringified withJSON.stringify()containing these fields. Compare against encrypted alternatives:expo-secure-store(Keychain on iOS, EncryptedSharedPreferences on Android),react-native-keychain(Keychain on iOS, Keystore on Android),@react-native-async-storage/async-storagecombined withrn-encrypted-storage. For Flutter, look forshared_preferencesstoring sensitive data vs.flutter_secure_storage. For native iOS, look forUserDefaultsstoring tokens vs.Keychain(SecItemAdd). Flag: auth tokens inAsyncStorage; raw JWTs stored inlocalStorage-equivalent; any string containing "token", "secret", "key", "password", "ssn", "creditCard" stored in plaintext storage. - Pass criteria: Auth tokens and sensitive PII are stored in platform-secure encrypted storage (Keychain on iOS, Keystore on Android, or equivalent). At least 1 implementation must be verified. No tokens or credentials found in
AsyncStorageorSharedPreferences. - Fail criteria: Auth tokens, session keys, or sensitive PII stored in
AsyncStorage,SharedPreferences, or equivalent unencrypted storage. - Skip (N/A) when: App is stateless (no local data persistence) or has no sensitive data storage (no auth, no PII collection).
- Detail on fail:
"JWT token stored via AsyncStorage.setItem('authToken', token) in src/services/auth.ts — AsyncStorage is unencrypted plaintext on all platforms"or"User profile including email and phone stored in AsyncStorage via JSON.stringify — sensitive PII in plaintext local storage" - Remediation: Unencrypted token storage is exploitable on jailbroken/rooted devices and creates privacy liability.
- Replace
AsyncStoragefor sensitive data withexpo-secure-store:import * as SecureStore from 'expo-secure-store'; // Store await SecureStore.setItemAsync('authToken', token); // Retrieve const token = await SecureStore.getItemAsync('authToken'); - For non-Expo React Native, use
react-native-keychain:import * as Keychain from 'react-native-keychain'; await Keychain.setGenericPassword('token', jwtValue); - Audit every
AsyncStorage.setItemcall — move tokens and PII to secure storage, leave only non-sensitive preferences (theme, language) in AsyncStorage
- Replace
External references
- cwe · CWE-312 — Cleartext Storage of Sensitive Information
- owasp:2021 · A02 — Cryptographic Failures
- nist:rev5 · SC-28 — Protection of Information at Rest
- hipaa · §164.312(a)(2)(iv) — Encryption and decryption of ePHI
- ssdf:800-218 · PW.4 — Reuse Existing, Well-Secured Software When Feasible
Taxons
History
- 2026-04-18·v1.0.0·Initial import from app-store-privacy-data·automated