No sensitive data stored in device shared preferences or NSUserDefaults
Why it matters
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.
Severity rationale
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.
Remediation
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.
Detection
-
ID:
no-cleartext-sensitive -
Severity:
critical -
What to look for: Count all
AsyncStorage.setItemandAsyncStorage.getItemcalls. 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')
External references
- cwe · CWE-312 — Cleartext Storage of Sensitive Information
- owasp:2021 · A02 — Cryptographic Failures
- gdpr · Art. 32 — Security of processing
- nist:rev5 · SC-28 — Protection of Information at Rest
Taxons
History
- 2026-04-18·v1.0.0·Initial import from mobile-permissions-privacy·automated