2.1.1
A hardening + diagnostics release. Drop-in upgrade from 2.1.0 — the on-disk format is unchanged.
✨ New & improved
- JVM persists without
jdk.unsupported(#32). Compose Desktop release distributables whose trimmedjlinkruntime omits the module no longer crash or drop writes — KSafe falls back to a software backend (same AES-256-GCM, key in a0700file) and migrates the data forward automatically when you add the module back. - Apple
secureRandomBytesnow sources fromSecRandomCopyBytes— the Security-framework CSPRNG backingSecKey*/ CryptoKit — for AES-256 master-key generation. KSafe.protectionInfois a live diagnostic — recomputed per access, so a JVM mid-process key-vault degrade is visible without restarting the app.KSafe.VERSION/KSafeProtectionInfo.kSafeVersionexpose the linked artifact version at runtime (handy for diagnostic UIs and audit logs).
🐛 Fixes
Data loss & correctness
- Apple: the orphan sweep no longer deletes the v2 master key (the critical fix above).
getOrCreateSecretno longer silently rotates a secret it can't read back (which permanently orphaned e.g. a SQLCipher database) — it now throws instead.- Linux: a locked / unreachable login keyring is no longer mistaken for "key absent" (which let the orphan sweep delete recoverable ciphertext).
- DataStore: switching a key between plain and encrypted no longer leaves a stale opposite-type value behind — including plaintext lingering on disk after a switch to encrypted.
- A
delete+HARDWARE_ISOLATEDputfor the same key inside one write-coalescing window no longer orphans the just-written entry. clearAll()is now serialized with concurrent writes — a write issued just before it can no longer land after the wipe and resurrect data.- JVM
appNamespacenow isolates the data file, not just the OS-vault keys — two desktop apps sharing afileNameno longer clobber one file (see Upgrade notes).
Robustness & security
- iOS biometrics: a cancelled prompt is now aborted (
LAContext.invalidate()) and the continuation guarded — no "already resumed" crash or orphaned system prompt. - Web:
applyBatchis now atomic — aQuotaExceededErrormid-batch can't leave a value written without its metadata. - Apple in-memory cache
clear()is now atomic (a write racingclearAll()could resurrect a cleared entry). - Biometric authorization caching uses a monotonic clock — moving the device clock backward can no longer extend a cached authorization past its window.
- Android: rooted /
userdebugdevices are now detected on modern Android (API 30+), without false-positivinguser-build emulators. - JVM
SecurityCheckerno longer failsKSafe(...)construction on a trimmedjlinkruntime missingjava.management.
🧭 Upgrade notes
- Drop-in from 2.1.0 — the on-disk format is unchanged.
getOrCreateSecretcan now throwIllegalStateExceptionwhen a secret exists but can't be read back (vault unavailable / key invalidated / corrupt), instead of silently minting a new one. Handle it at the call site.- JVM
appNamespace: the on-disk path changes for apps that explicitly set it — data now lives in a per-namespace subdirectory. Existing data is migrated forward automatically (copied, not moved), and apps that don't setappNamespaceare unaffected.
📋 Full, detailed notes (mechanisms, regression tests, per-file specifics): CHANGELOG.md
Full Changelog: 2.1.0...2.1.1