Sensitive value written to an unprotected plaintext file
ID |
dart.storage.unprotected_file_storage |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Storage |
Language |
Dart |
Description
Sensitive material (token, password, secret, JWT, PIN, OTP) is written to a plaintext file via dart:io File.writeAsString / writeAsBytes (sync or async). The app-private file is readable on rooted / jailbroken devices and via OS backups.
Rationale
Like SharedPreferences, files created under the app’s documents / cache / support directories live inside the OS sandbox — but the sandbox does not encrypt the file content. Any of the following exposes the bytes:
-
Rooted / jailbroken device file-system access.
-
adb backup(Android < 13 whenallowBackup="true"). -
iCloud Drive / Google Drive backup of the app container.
-
Forensic image of the device.
-
A second app that exploits a separate file-permission bug.
Storing tokens, passwords, JWTs, OTPs, recovery phrases, or
hard-coded API keys in plain files therefore must be considered
effectively public. The rule narrows by the IDENTIFIER name being
written (token / password / secret / apiKey / jwt etc.)
to avoid flagging legitimate writes of non-sensitive content.
The following code illustrates a vulnerable pattern detected by this rule:
class WriteSecrets {
Future<void> writePlain(File f, String token, String apiKey, String password, String secret) async {
// VULNERABLE: Sensitive value written to an unprotected plaintext file
await f.writeAsString(token);
// VULNERABLE: Sensitive value written to an unprotected plaintext file
await f.writeAsString(apiKey);
// VULNERABLE: Sensitive value written to an unprotected plaintext file
await f.writeAsString(password);
// VULNERABLE: Sensitive value written to an unprotected plaintext file
await f.writeAsString(secret, mode: FileMode.append);
// VULNERABLE: Sensitive value written to an unprotected plaintext file
f.writeAsStringSync(token);
// VULNERABLE: Sensitive value written to an unprotected plaintext file
await f.writeAsBytes(secret.codeUnits);
// VULNERABLE: Sensitive value written to an unprotected plaintext file
f.writeAsBytesSync(password.codeUnits);
}
}
Remediation
Use flutter_secure_storage for small secrets, or encrypt the
bytes yourself using a key derived from a user secret (Argon2id /
scrypt / PBKDF2 >= 600 000 iterations) before writing. For large
blobs, encrypt with AES-GCM and store the key in secure storage.
// Vulnerable
await file.writeAsString(token);
file.writeAsBytesSync(secret.codeUnits);
// Fixed: small value -> flutter_secure_storage
const storage = FlutterSecureStorage();
await storage.write(key: 'token', value: token);
// Fixed: large blob -> encrypt-before-write
final cipher = AesGcm.with256bits();
final box = await cipher.encrypt(plaintextBytes,
secretKey: derivedKey, nonce: cipher.newNonce());
await file.writeAsBytes(box.concatenation());
References
-
OWASP Top 10 2025 - A04 : Cryptographic Failures.
-
OWASP Top 10 2025 - A06 : Insecure Design.
-
CWE-313 — Cleartext Storage in a File or on Disk
-
Talsec — OWASP Mobile Top 10 for Flutter M9 — Insecure data storage in Flutter / Dart
-
Cossack Labs — Flutter application security considerations — Notes on at-rest data protection limitations in Flutter / mobile