Swift Cleartext Storage in UserDefaults

Sensitive data stored in cleartext in Swift UserDefaults

Description

UserDefaults stores application preferences and small settings. Saving sensitive values such as authentication tokens, session IDs, API keys, or passwords in cleartext in UserDefaults or NSUbiquitousKeyValueStore can expose them through device backups, debug extraction, jailbroken devices, or iCloud key-value synchronization.

Potential impact

  • Authentication-token or session theft
  • Exposure of API keys and client secrets
  • Spread of sensitive data through iCloud synchronization or backups
  • Account access using stolen values

Remediation

  1. Avoid storing sensitive values on the device where possible.
  2. If storage is necessary, use Keychain Services or a vetted encrypted store with separate key management. Secure Enclave protects supported cryptographic keys; it is not a general store for secret values.
  3. Keep only non-sensitive preferences, such as theme, language, or onboarding status, in UserDefaults.
  4. Use NSUbiquitousKeyValueStore only for non-sensitive settings that need iCloud synchronization.

Examples

Before

swift
func saveToken(token: String) {
    UserDefaults.standard.set(token, forKey: "access_token")
}

After

swift
import Foundation
import Security

enum KeychainStorageError: Error {
    case unexpectedStatus(OSStatus)
}

func saveToken(token: Data) throws {
    let item: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: "com.example.app.auth",
        kSecAttrAccount as String: "access_token",
        kSecAttrAccessible as String:
            kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
        kSecValueData as String: token
    ]

    let addStatus = SecItemAdd(item as CFDictionary, nil)
    if addStatus == errSecSuccess { return }
    guard addStatus == errSecDuplicateItem else {
        throw KeychainStorageError.unexpectedStatus(addStatus)
    }

    let match: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: "com.example.app.auth",
        kSecAttrAccount as String: "access_token"
    ]
    let updates: [String: Any] = [
        kSecAttrAccessible as String:
            kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
        kSecValueData as String: token
    ]
    let updateStatus = SecItemUpdate(
        match as CFDictionary, updates as CFDictionary)
    guard updateStatus == errSecSuccess else {
        throw KeychainStorageError.unexpectedStatus(updateStatus)
    }
}

Explanation:

  • Before: A cleartext authentication token in UserDefaults may be exposed through application sandbox extraction, backups, or debugging environments.
  • After: Keychain Services stores the value with device-only, passcode-required accessibility. The code checks insertion results and explicitly updates both value and accessibility for an existing item. Other failures are returned to the caller instead of falling back to weaker storage.

References