AsyncStorage on React Native is unencrypted, stored in plaintext on the device filesystem, and accessible to any process with root access or via backup extraction on Android. Storing auth tokens, API keys, or PII in AsyncStorage violates GDPR Art. 32 (security of processing) and NIST SP 800-53 SC-28 (protection of information at rest). On a jailbroken iOS device or a rooted Android device — which a meaningful fraction of users run — the AsyncStorage database file is readable without authentication. OWASP Mobile Top 10 A02 (Insecure Data Storage) lists unencrypted AsyncStorage for tokens as a critical vulnerability. A compromised auth token allows full account takeover; a compromised API key enables billing fraud.
Critical because auth tokens in plaintext AsyncStorage are recoverable by any process with filesystem access on rooted/jailbroken devices, enabling complete account takeover — a direct OWASP A02 and GDPR Art. 32 violation.
Move all auth tokens, API keys, and PII from AsyncStorage to expo-secure-store (Expo) or native Keychain/Keystore. Restrict AsyncStorage to non-sensitive preferences only.
import * as SecureStore from 'expo-secure-store'
import AsyncStorage from '@react-native-async-storage/async-storage'
// Sensitive: use SecureStore (encrypted, Keychain-backed on iOS)
await SecureStore.setItemAsync('authToken', token)
await SecureStore.setItemAsync('refreshToken', refreshToken)
// Non-sensitive only: use AsyncStorage
await AsyncStorage.setItem('colorScheme', 'dark')
await AsyncStorage.setItem('lastViewedTab', 'home')
Search for AsyncStorage.setItem calls with keys like token, auth, key, password, email, or user — those are the most common violations. SecureStore keys are bounded at 2048 bytes; store a reference ID and fetch larger payloads from an encrypted database if needed.
ID: mobile-permissions-privacy.privacy-compliance.no-cleartext-sensitive
Severity: critical
What to look for: Count all AsyncStorage.setItem and AsyncStorage.getItem calls. For each, classify the data stored as sensitive (auth tokens, API keys, PII, payment info) or non-sensitive (preferences, theme settings). Quote the actual storage key names found.
Pass criteria: Zero sensitive data items (auth tokens, API keys, PII, payment info) are stored in AsyncStorage, NSUserDefaults, or SharedPreferences — at least 100% of sensitive data uses secure storage (Keychain, Keystore, expo-secure-store). Report the count even on pass: "Found N AsyncStorage calls, none store sensitive data."
Fail criteria: Any sensitive data is found stored unencrypted in AsyncStorage, NSUserDefaults, or SharedPreferences. OR app stores auth tokens in AsyncStorage without encryption.
Skip (N/A) when: App stores no data at all (no AsyncStorage, SecureStore, or similar calls found).
Detail on fail: Name the sensitive data and quote the storage key. Example: "Auth token stored unencrypted in AsyncStorage with key 'authToken'" or "User email stored in AsyncStorage with key 'userEmail' without encryption"
Cross-reference: For secure credential handling patterns in authentication flows, the Auth & Session Security audit covers token storage and session management.
Remediation: Sensitive data must be encrypted or stored in platform-secure storage (Keychain on iOS, Keystore on Android). Use expo-secure-store:
import * as SecureStore from 'expo-secure-store'
// Store sensitive data
await SecureStore.setItemAsync('authToken', token)
// Retrieve sensitive data
const token = await SecureStore.getItemAsync('authToken')
// For non-sensitive data only
await AsyncStorage.setItem('userPreference', 'darkMode')