From grimoire
Stores sensitive mobile data using iOS Keychain and Android Keystore instead of plaintext files or SharedPreferences/UserDefaults. Follows OWASP MASVS secure storage requirements.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-secure-mobile-storageThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Store sensitive mobile app data using platform-provided secure enclaves (iOS Keychain, Android Keystore) and encrypted databases — never in plaintext files, SharedPreferences, or UserDefaults.
Store sensitive mobile app data using platform-provided secure enclaves (iOS Keychain, Android Keystore) and encrypted databases — never in plaintext files, SharedPreferences, or UserDefaults.
Adopted by: OWASP Mobile Top 10 2024 M9 (Insecure Data Storage) is a top mobile vulnerability. OWASP MASVS (Mobile Application Security Verification Standard) MSTG-STORAGE-1 through MSTG-STORAGE-14 specify secure storage requirements. Apple's iOS Security Guide mandates Keychain for sensitive data. Google's Android Security Best Practices require Keystore for cryptographic keys. PCI DSS v4.0 Requirement 3 and HIPAA both mandate encryption for stored sensitive data on mobile. Impact: The OWASP Mobile Top 10 consistently ranks insecure data storage as a critical finding across mobile app security assessments. Common findings: app tokens stored in plaintext SQLite databases (accessible after physical device access or backup extraction), encryption keys stored in UserDefaults/SharedPreferences (readable without root on unencrypted devices), and PII cached in unencrypted temporary files. Physical device access, iCloud/adb backup extraction, and root/jailbreak all enable reading non-secure storage. Why best: Encrypting files with a key stored in the same location as the encrypted data is the common alternative — it's equivalent to locking a safe and taping the combination to the door. Platform-provided secure hardware enclaves (Secure Enclave on iOS, TEE/StrongBox on Android) store keys in hardware that cannot be extracted even with root access.
Sources: OWASP Mobile Top 10 2024 M9; OWASP MASVS MSTG-STORAGE; Apple iOS Security Guide; Android Security Best Practices; CWE-312
iOS — use Keychain for all secrets and tokens:
import Security
func saveToKeychain(key: String, data: Data) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
// Never use kSecAttrAccessibleAlways — accessible even when locked
]
SecItemDelete(query as CFDictionary) // remove existing
return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
}
func loadFromKeychain(key: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true
]
var result: AnyObject?
SecItemCopyMatching(query as CFDictionary, &result)
return result as? Data
}
Use kSecAttrAccessibleWhenUnlockedThisDeviceOnly for most secrets — requires device unlock, non-exportable.
Android — use EncryptedSharedPreferences and Android Keystore:
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
// Create master key backed by Android Keystore
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
// EncryptedSharedPreferences — transparently encrypted
val securePrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// Use like normal SharedPreferences
securePrefs.edit().putString("auth_token", token).apply()
val token = securePrefs.getString("auth_token", null)
Never use these for sensitive data:
| Storage mechanism | Why it's insecure |
|---|---|
UserDefaults (iOS) | Plaintext plist, readable from backup |
SharedPreferences without encryption | Plaintext XML, readable with root |
| SQLite without encryption | Readable from backup or filesystem |
| Files in Documents/ (iOS) | Included in iCloud backup by default |
| External storage (Android) | World-readable before Android 10 |
| Log output | Readable from ADB on debug builds |
Use SQLCipher for encrypted databases:
// Android — SQLCipher
import net.sqlcipher.database.SQLiteDatabase
SQLiteDatabase.loadLibs(context)
val db = SQLiteDatabase.openOrCreateDatabase(dbPath, getDatabaseKey(), null)
// getDatabaseKey() should return key from Android Keystore
// iOS — SQLCipher (via Swift Package Manager)
let db = try Connection(dbPath)
try db.key(getKeyFromKeychain())
Exclude sensitive files from backups:
// iOS — mark file as excluded from iCloud backup
var url = URL(fileURLWithPath: sensitiveFilePath)
var values = URLResourceValues()
values.isExcludedFromBackup = true
try url.setResourceValues(values)
<!-- Android — exclude files from Auto Backup (AndroidManifest.xml) -->
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules">
<!-- res/xml/backup_rules.xml -->
<full-backup-content>
<exclude domain="database" path="sensitive.db"/>
<exclude domain="sharedpref" path="secure_prefs.xml"/>
</full-backup-content>
Clear sensitive data from memory when no longer needed:
// Overwrite sensitive strings in memory
var password = getPassword()
defer {
password = String(repeating: "\0", count: password.count)
}
authenticate(with: password)
kSecAttrAccessibleWhenUnlockedThisDeviceOnly / setUserAuthenticationRequired(true) for the highest-value secrets.StrongBox-backed keys are stored in dedicated hardware security module — use for the most sensitive keys on supported devices.#if DEBUG / BuildConfig.DEBUG guards to prevent logging tokens in release builds.kSecAttrAccessGroup to restrict access.npx claudepluginhub jeffreytse/grimoire --plugin grimoireIdentifies and exploits insecure local data storage in Android/iOS mobile apps, including unencrypted databases, SharedPreferences, world-readable files, and Keychain/Keystore misuse. For OWASP M9 pentesting.
Identifies and exploits insecure local data storage in Android/iOS apps: unencrypted databases, world-readable files, plaintext credentials, and improper keychain/keystore usage. For mobile penetration testing and OWASP M9 assessments.
Identifies and exploits insecure local data storage in Android/iOS apps: unencrypted databases, world-readable files, plaintext credentials, and improper keychain/keystore usage. For mobile penetration testing and OWASP M9 assessments.