v0.15 is out: rules that were silently dropped on the shipped engine now run, and scans skip your test and mock files by default. Read more →
HIGH AI PREVALENCE: HIGH auth.kotlin.android.token-in-sharedprefs

An auth token / secret is written to plain SharedPreferences (prefs.edit().putString("auth_token", ...)).

Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.

Why this matters

SharedPreferences is an unencrypted XML file in the app's private storage: on a rooted device, via a device backup, or through any local-file exposure it is read in the clear, leaking the credential (CWE-312). AI-generated Android samples reach for plain SharedPreferences because it is the "hello world" of persistence and never switch to encrypted storage.

Store credentials in EncryptedSharedPreferences (androidx.security.crypto) backed by the Android Keystore instead: val prefs = EncryptedSharedPreferences.create( context, "secure_prefs", masterKey, AES256_SIV, AES256_GCM) prefs.edit().putString("auth_token", token).apply()

VULNERABLE
vulnerable.kt
import android.content.Context

class TokenStore(context: Context) {
    private val prefs = context.getSharedPreferences("app", Context.MODE_PRIVATE)

    fun save(token: String) {
        // ruleid: auth.kotlin.android.token-in-sharedprefs
        prefs.edit().putString("auth_token", token).apply()
    }

    fun saveSecret(secret: String) {
        // ruleid: auth.kotlin.android.token-in-sharedprefs
        prefs.edit { putString("client_secret", secret) }
    }
}
SAFE
safe.kt
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences

class TokenStore(context: Context) {
    private val plainPrefs = context.getSharedPreferences("app", Context.MODE_PRIVATE)

    fun saveEncrypted(context: Context, token: String) {
        val prefs = EncryptedSharedPreferences.create(
            context, "secure_prefs", masterKey, AES256_SIV, AES256_GCM)
        // encrypted storage: safe
        prefs.edit().putString("auth_token", token).apply()
    }

    fun saveUsername(name: String) {
        // non-credential key: safe
        plainPrefs.edit().putString("username", name).apply()
    }
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.kotlin.android.token-in-sharedprefs -- <reason>

References

https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences ↗https://cwe.mitre.org/data/definitions/312.html ↗