rea
Reverse engineer anything with agents, from app behavior down to native binaries.
Skill for the KSafe Kotlin library, which persists variables, Compose State, StateFlow, and serializable objects with encryption by default across Android, iOS, macOS, Desktop, and Web. It also covers key rotation and biometrics.
Claude Code
> /plugin marketplace add ioannisa/KSafe> /plugin install ksafe@ksafe
Codex
$ npx skills add ioannisa/KSafe -a codex
Cursor
$ npx skills add ioannisa/KSafe -a cursor
Gemini CLI
$ npx skills add ioannisa/KSafe -a gemini-cli
GitHub Copilot
$ copilot plugin marketplace add ioannisa/KSafe$ copilot plugin install ksafe@ksafe
OpenCode
$ npx skills add ioannisa/KSafe -a opencode
Windsurf
$ npx skills add ioannisa/KSafe -a windsurf
KSafe is a secure-by-default Kotlin Multiplatform key/value persistence library. Persist ordinary Kotlin variables, Compose MutableState, MutableStateFlow, and @Serializable objects across app restarts with one API on Android, iOS, macOS, JVM/Desktop, WASM, and Kotlin/JS. Encrypted (AES-256-GCM) by default; plain per-entry with mode = KSafeWriteMode.Plain.
var counter by ksafe(0)
counter++ // auto-encrypted (AES-256-GCM), auto-persisted, survives process death
Read and write it like any normal Kotlin variable — no suspend, no runBlocking, no DataStore boilerplate, no explicit encrypt/decrypt. Reads hit a hot in-memory cache; writes encrypt and flush in the background — synchronous, but never blocking. Reach for the suspend API (get / put) only when you want to await the disk flush.
Extras when you encrypt: biometrics (Face ID / Touch ID / Fingerprint — optional standalone ksafe-biometrics module) · root/jailbreak detection (WARN/BLOCK + analytics callback) · memory policy (RAM-exposure modes) · a one-line hardware-isolated DB passphrase for SQLCipher / SQLDelight / Room.
KSafe ships an agentskills.io-compatible skill — skills/ksafe/SKILL.md — that teaches any AI agent (Claude Code, Codex, Gemini CLI, Copilot CLI, Junie) KSafe's patterns, anti-patterns and gotchas, so the code it writes for you is the code this README describes.
Claude Code — run both, in this order, once. Restart the session afterwards; skills load at session start:
/plugin marketplace add ioannisa/KSafe # register this repo as a plugin source
/plugin install ksafe@ksafe # install the skill from it
Any other agent — one command, then pick your agents from its prompt:
npx skills add ioannisa/KSafe
Forcing an update, installing without any tooling, and why you should not also hand-copy SKILL.md: docs/AI_AGENTS.md.
KSafe in action across many scenarios: KSafeDemo — Compose Multiplatform app.
| Author's Video | Philipp Lackner's Video | Jimmy Plazas's Video |
|---|---|---|
| KSafe - Kotlin Multiplatform Encrypted DataStore Persistence Library | How to Encrypt Local Preferences In KMP With KSafe | Encripta datos localmente en Kotlin Multiplatform con KSafe - Ejemplo + Arquitectura |
// commonMain or Android-only build.gradle(.kts)
implementation("eu.anifantakis:ksafe:3.3.0")
implementation("eu.anifantakis:ksafe-compose:3.3.0") // ← Compose state (optional)
implementation("eu.anifantakis:ksafe-biometrics:3.3.0") // ← Biometric auth (optional)
Skip
ksafe-composeif you don't use Jetpack Compose ormutableStateOfpersistence.Skip
ksafe-biometricsif you don't need Face ID / Touch ID / Fingerprint verification. The biometrics module is fully independent — it has no dependency on:ksafeand can be used on its own to protect any action in your app.
Note:
kotlinx-serialization-jsoncomes in transitively — don't add it yourself.
Required only if you store @Serializable data classes. Add it to libs.versions.toml:
[versions]
kotlin = "2.2.21"
[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
then apply it in build.gradle.kts:
plugins {
//...
alias(libs.plugins.kotlin.serialization)
}
// Android
val ksafe = KSafe(context)
// iOS / macOS / JVM / WASM / JS
val ksafe = KSafe()
With Koin (recommended for KMP):
// Android
actual val platformModule = module {
single { KSafe(androidApplication()) }
}
// iOS / macOS / JVM / WASM / JS
actual val platformModule = module {
single { KSafe() }
}
Multi-instance setups, web awaitCacheReady(), custom storage directories, key namespacing for Desktop/Web (appNamespace), and AES key-size configuration: docs/SETUP.md.
Compose Desktop release builds: add
modules("jdk.unsupported", "java.management")tonativeDistributionsfor OS-backed key custody — why, and what happens without it: docs/JVM_PROTECTION.md.
There are two ways to reach your data, and they share the same store and the same hot cache, so you can mix them freely. Full reference (Compose policy, cross-screen sync, write modes, nullables, deletion, full ViewModel): docs/USAGE.md.
You declare one variable per value. KSafe persists it, encrypts it, and keeps it in the hot cache, so reads and writes are synchronous and never suspend.
Property delegation — the key is the property name:
var counter by ksafe(0)
counter++
Direct handle (3.2.0+) — the same call without by. Keep the handle in a val, pass it around, read and write .value. The key must be given explicitly, because there is no property name to infer it from:
val counter = ksafe(0, key = "counter") // KSafeReference<Int>
counter.value++
No variable per key. You address any key, at any time, straight on the instance. Each operation comes in two forms: a suspend one that waits for the disk flush, and a Direct one that returns at once, serves reads from the hot cache and flushes writes in the background (about 1000x faster for bulk work).
// Put
ksafe.put("profile", user) // suspend — returns after the disk flush
ksafe.putDirect("counter", 42) // non-suspend — background flush
// Get
val loaded: User = ksafe.get("profile", User())
val n = ksafe.getDirect("counter", 0)
// Delete
ksafe.delete("profile")
ksafe.deleteDirect("counter")
// Wipe the store — every value and the keys that protected them (logout)
ksafe.clearAll() // suspend
Reactive reads that pick up changes made anywhere: another screen, a background sync, a delegate against the same key. Four shapes, and the same two doors as above — the property names the key, or you do:
| Shape | Type | Hot or cold | Writes | Scope | Same thing, key spelled out |
|---|---|---|---|---|---|
asFlow | Flow<T> | cold | — | none | getFlow(key, default) |
asWritableFlow | WritableKSafeFlow<T> | cold | set(value) | none | — |
asStateFlow | StateFlow<T> | hot, .value | — | needed | getStateFlow(key, default, scope) |
asMutableStateFlow | MutableStateFlow<T> | hot, .value | .value =, update {} | needed | — |
Cold — nothing runs until someone collects, so there is no scope to manage:
val toggleMode: Flow<Boolean> by ksafe.asFlow(defaultValue = false)
toggleMode.collect { on -> render(on) }
ksafe.putDirect("toggleMode", true) // updated from anywhere — the collector above sees true
// Writable: one declaration you both collect and write through
val themeMode: WritableKSafeFlow<ThemeMode> by ksafe.asWritableFlow(ThemeMode.DEVICE)
themeMode.collect { mode -> applyTheme(mode) }
themeMode.set(ThemeMode.DARK) // persists, and every collector sees it
Hot — a current value is always there, and that is what the scope pays for. Something has to sit on the store, watch for changes made elsewhere and push them in; that watcher is a coroutine, and it must die with your ViewModel:
val isLoggedIn: StateFlow<Boolean> by ksafe.asStateFlow(true, viewModelScope)
isLoggedIn.value // read it any time, with no collector at all
ksafe.putDirect("isLoggedIn", false) // every collector sees false
// Writable: the _state / state pattern, persisted
private val _count by ksafe.asMutableStateFlow(0, viewModelScope)
val count = _count.asStateFlow()
_count.update { it + 1 } // persists
_count.value = 42 // persists
Without the delegate — the same shapes and the same types, with the key spelled out. Note that a flow is always bound to one key: unlike put or getDirect, there is no flow over any key.
// Cold: call it inline, as often as you like — a cold Flow starts nothing on its own
ksafe.getFlow("isLoggedIn", defaultValue = true).collect { loggedIn -> render(loggedIn) }
// Hot: call it ONCE and keep the result — every call runs stateIn() and starts its own watcher
val isLoggedIn: StateFlow<Boolean> = ksafe.getStateFlow("isLoggedIn", true, viewModelScope)
Warning Call
getStateFlowonce and keep the result. Every call runsstateIn()and launches its own watcher coroutine in the scope you pass, so calling it inline, in a loop, or inside a composable leaks one watcher per call until that scope is cancelled. Theby ksafe.asStateFlow(...)delegate is safe by construction — it builds itsStateFlowon first read and hands back the same instance forever after.getFlowis cold and costs nothing: call it as often as you like.
For the cold shapes the two forms are interchangeable — pick whichever reads better.
There is no writable shape without the delegate, and none is needed: to write, use put or putDirect, and every reader of that key sees it — a delegated flow in a ViewModel, a getFlow on another screen, a Compose state. One store, one cache.
Persistent state inside a @Composable body. The rememberSaveable analogue that also survives app restarts; the key resolves to the property name and no ViewModel is needed. KSafe is @Stable, so it can be passed as a parameter without breaking skipping. The default mode here is Plain, because this is UI state, not a secret. Requires ksafe-compose.
@Composable
fun TabbedScreen(ksafe: KSafe) {
var currentTab by ksafe.rememberKSafeState(Tab.Home)
// ...
}
Reverse engineer anything with agents, from app behavior down to native binaries.
A skill pack for authorized reverse engineering, security research, and penetration testing that routes tasks to the right skill and sets up needed tools on demand. It covers API security assessment and Android APK analysis, among others.
Gives an AI agent the step-by-step workflow a senior security analyst follows, covering when to use each technique, what to check first, how to run it and how to verify the result. Covers incident response, forensics, threat intelligence, malware analysis, penetration testing and red teaming.
Agent skill + stdlib Python service to strip multi-vendor AI provenance marks from text and files, for privacy and hygiene on content you own. The skill is a thin client: it drives the machinery over HTTP, so the agent host needs no Python.
Install it with the command on its page. It includes ksafe. Its skills do not fire on their own yet. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it