Skip to content
Security
Skill

/ksafe

Required before any reply that touches KSafe (`by ksafe(...)`, `ksafe.get/put`, :ksafe-compose, :ksafe-biometrics), even a 'can KSafe do X?' question or a one-line change that looks like plain Kotlin. KSafe's signatures, defaults and overloads changed in recent releases, so

From plugin
ksafe
3431 skill

Claude Code

$ npx -y skills add ioannisa/KSafe --skill ksafe --agent claude-code

SKILL.md

ksafe.SKILL.md
name: ksafe
description: |
  Required before any reply that touches KSafe (`by ksafe(...)`, `ksafe.get/put`,
  :ksafe-compose, :ksafe-biometrics), even a 'can KSafe do X?' question or a one-line change
  that looks like plain Kotlin. KSafe's signatures, defaults and overloads changed in recent
  releases, so answers from memory give wrong code. Typical: making one value unencrypted,
  passing a KSerializer for a generic T, faking KSafe in commonTest, compile errors,
  crashes, Desktop packaging. Also use when KSafe is unnamed but Kotlin/Compose
  Multiplatform code must keep secrets or settings on device: tokens, PINs, passwords, DB
  passphrases, encrypted prefs, a testable local data source, choosing or replacing a KMP
  secure-storage library (EncryptedSharedPreferences, DataStore, KVault, Multiplatform
  Settings), Face ID/fingerprint/Windows Hello gating, key rotation, proving keys sit in
  StrongBox/Secure Enclave/Keychain. Skip storage work with no KMP target and no KSafe (pure
  Swift, Android-only, browser).

KSafe — Kotlin Multiplatform Encrypted Persistence

You are about to write or modify code that uses **KSafe**: a one-API encrypted key-value store covering Android, iOS, native macOS, JVM Desktop, Kotlin/WasmJS, and Kotlin/JS. Encrypted values use AES-GCM. Keep **payload encryption**, **durable key custody**, and the **working key in process memory** conceptually separate: the secure paths protect a long-lived key or KEK in a platform vault, while documented software fallbacks can keep key material in a permission-protected file. Use `protectionInfo` to report the route that was actually achieved.

This skill is self-contained — it covers everything you need to **set up** and **use** KSafe correctly. Always prefer the **property delegate** as the default API.

**The single most important fact: KSafe is encrypted by default.** `ksafe(value)` encrypts. You opt *out* for non-secret values with `mode = KSafeWriteMode.Plain`.

---

Key-custody matrix (this is what makes KSafe interesting)

| Platform | Default encrypted route | `HARDWARE_ISOLATED` upgrade / fallback | |---|---|---| | Android | Relaxed: non-exportable Keystore KEK wraps a DEK stored as ciphertext; the unwrapped DEK is cached in RAM. Strict unlock mode performs payload operations in Keystore. | Per-entry StrongBox when available; normal Keystore fallback otherwise. | | iOS / native macOS | AES key stored in Keychain, loaded into the app for CryptoKit payload operations. | Secure Enclave EC key wraps a per-entry AES DEK; ordinary Keychain fallback otherwise. Simulator-only entitlement failure can use a reported software file fallback. | | JVM Desktop | AES key protected by Windows DPAPI, macOS login Keychain, or Linux Secret Service, then loaded for JCE payload operations. | No stronger common tier. A reported permission-protected file fallback is used only where no usable OS vault exists or the user explicitly opts out. | | WasmJS / JS | Non-extractable WebCrypto AES `CryptoKey` in IndexedDB. | No stronger tier; outside a secure context encrypted operations are non-operational rather than silently written plain. |

When a stronger tier is absent (for example no StrongBox, no Secure Enclave, or no supported desktop OS vault), KSafe **degrades to the documented next-best path and reports the degrade** through `KSafe.protectionInfo`. If a real JVM OS vault exists but is temporarily unreachable, KSafe fails closed instead of inventing a replacement software key. Never trade operability for silent data loss.

---

SETUP

Dependencies

// commonMain (or Android-only) build.gradle.kts
implementation("eu.anifantakis:ksafe:<latest>")              // core
implementation("eu.anifantakis:ksafe-compose:<latest>")      // optional: Compose state
implementation("eu.anifantakis:ksafe-biometrics:<latest>")   // optional: biometric prompts

`kotlinx-serialization-json` comes transitively — don't add it yourself. If you store `@Serializable` classes, apply the kotlin-serialization plugin in your app.

Construction

// Android — pass applicationContext (NOT an Activity context — it leaks)
val ksafe = KSafe(applicationContext)

// iOS / macOS / JVM / WasmJS / JS — no context
val ksafe = KSafe()
val ksafe = KSafe(fileName = "auth")   // isolated named instance

Full factory parameters (all platforms except where noted):

KSafe(
    context: Context,                    // Android ONLY — applicationContext
    fileName: String? = null,            // null = default instance; else isolates storage
    lazyLoad: Boolean = false,           // ignored on web
    memoryPolicy: KSafeMemoryPolicy = KSafeMemoryPolicy.LAZY_PLAIN_TEXT,
    config: KSafeConfig = KSafeConfig(),
    securityPolicy: KSafeSecurityPolicy = KSafeSecurityPolicy.Default,
    baseDir: File? = null,               // JVM/Android custom dir; iOS uses `directory: String?`
)

KSafeConfig(
    aesKeySize: KSafeAesKeySize = KSafeAesKeySize.BITS_256, // BITS_128 or BITS_256; every platform
    requireUnlockedDevice: Boolean = false,  // default unlock policy for encrypted writes
    json: Json = KSafeDefaults.json,         // custom serialization
    appNamespace: String? = null,            // multi-app isolation (see below)
    keyRotationPolicy: KSafeKeyRotationPolicy = KSafeKeyRotationPolicy.Never,  // see Key rotation
    keyRotationRetryAttempts: Int = 3,       // next-instance retries for skipped work; 0 = off
)

Recommended DI setup (Koin) — mode-typed views over ONE instance (3.1.0+)

Encryption adds per-value overhead (AES-GCM + JSON envelope; ~µs since 2.1.2, but never free). For non-secret data — theme, last screen, UI flags — that overhead is wasted. Since 3.1.0 the recommended pattern is **one store, typed views**: `KSafePlain` / `KSafeEncrypted` / `KSafeHardwareIsolated` wrap an existing instance and freeze the write mode at the type level, so no call site carries a `mode =` argument the author can forge

Read more
@ioannisa
Ships withksafe

Encrypted by default. Plain (unencrypted) when needed. Persist variables, Compose State, StateFlow, and serializable objects across Android, iOS, macOS, Desktop, and Web Easy to use by design — plus key rotation, cross-platform biometrics, and an encryption

Stars on GitHub

Momentum

stars in total
343
Star history of ioannisa/KSafe on GitHub, a chart by star-history.com

More skills from other plugins.

api-security

api-security

Use for authorized security assessment of REST, GraphQL, WebSocket, or SOAP APIs, including discovery, authentication,…

@zhaoxuya520@zhaoxuya520Install Skill
nemoclaw-user-guide

nemoclaw-user-guide

Find official NemoClaw documentation for installation, configuration, operation, or troubleshooting. Also handles requested docs…

nemoclaw11d23k
@nvidia@nvidiaInstall Skill

Questions about ksafe.

Which coding agents can use ksafe?
ksafe installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.