Sensitive value stored in cleartext SharedPreferences
ID |
dart.storage.cleartext_storage_preferences |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Storage |
Language |
Dart |
Description
Sensitive material (token, password, secret, API key, session id, PIN, OTP) is written to SharedPreferences. SharedPreferences are unencrypted XML / Plist files in the app sandbox — readable on rooted / jailbroken devices and via OS backups.
Rationale
shared_preferences is the default per-app key-value store in Flutter
and is intentionally NOT encrypted. The backing files live in
/data/data/<pkg>/shared_prefs/ on Android and the app’s Plist
container on iOS. They are protected by the OS sandbox, but that
protection fails in several common cases:
-
Rooted / jailbroken devices (consumer-installed root kits, enterprise BYOD with root for testing, malware-installed root).
-
Android cloud backup (
android:allowBackup="true", the default in older targets) — preferences are exfiltrated viaadb backuporbmgr. -
iCloud / Google Drive cross-device sync of the app container.
-
Forensic acquisition (
Cellebrite,Magnet AXIOM) of a seized device.
Storing authentication material here means anyone with file-system
access to the device has the user’s credentials. The rule narrows
by KEY NAME (token, password, apiKey, jwt, etc.) so generic
preferences like theme / locale / counter don’t fire.
The following code illustrates a vulnerable pattern detected by this rule:
class StorePrefs {
Future<void> sensitiveKeys() async {
final prefs = await SharedPreferences.getInstance();
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setString('token', 'value');
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setString('access_token', 'value');
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setString('refresh_token', 'value');
await prefs.setString('apiKey', 'value');
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setString('api_key', 'value');
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setString('user_password', 'value');
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setString('jwt', 'value');
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setString('session_id', 'value');
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setString('credential', 'value');
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setStringList('auth_tokens', []);
await prefs.setBool('isAuthenticated', true);
// VULNERABLE: Sensitive value stored in cleartext SharedPreferences
await prefs.setInt('pin', 1234);
}
}
Remediation
Use flutter_secure_storage for any value the user expects to be
confidential. It’s backed by Keychain on iOS (with appropriate
accessibility class) and EncryptedSharedPreferences / Keystore on
Android. For short-lived session material, prefer in-memory storage
bound to the app process lifetime.
// Vulnerable
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', authToken);
await prefs.setString('pin', userPin);
// Fixed
const storage = FlutterSecureStorage();
await storage.write(key: 'token', value: authToken);
await storage.write(key: 'pin', value: userPin);
References
-
OWASP Top 10 2025 - A04 : Cryptographic Failures.
-
OWASP Top 10 2025 - A06 : Insecure Design.
-
CWE-312 — Cleartext Storage of Sensitive Information
-
Talsec — OWASP Mobile Top 10 for Flutter M9 — Insecure data storage in Flutter / Dart with concrete scenarios
-
package:flutter_secure_storage (pub.dev) — Keychain / EncryptedSharedPreferences-backed key-value store — the standard alternative