github jdx/fnox v1.25.1
v1.25.1: Keychain reliability and dependency refreshes

5 hours ago

A patch release focused on the OS keychain provider: a deadlock and dialog-storm fix on macOS, plus a clean migration to keyring-core v1 and keepass 0.12.

Fixed

Keychain provider no longer deadlocks the runtime or stacks dialogs (#495) -- @jdx

Resolving several keychain-backed secrets at once on macOS could hang fnox indefinitely (see discussion #489). The default batch path fans out 10 concurrent reads, which surfaced up to 10 overlapping "Always Allow / Allow / Deny" Security dialogs and pinned every tokio worker thread waiting for the user — deadlocking the runtime.

Two changes fix this:

  • All keyring-core calls (set_password, get_password, delete_credential) now run via tokio::task::spawn_blocking, so a blocked Security dialog never pins a runtime worker.
  • The keychain provider overrides get_secrets_batch to resolve entries serially, so dialogs appear one at a time and Ctrl-C stays responsive.

fnox set against the keychain and test_connection are routed through the blocking pool too.

KeePass provider works with keepass 0.12 (#494) -- @jdx

The 0.10 → 0.12 bump made Group.groups, Group.entries, Database.root, and friends pub(crate), with access now going through Database::root(), GroupRef/GroupMut accessors, and add_group / add_entry. The provider has been rewritten to match:

  • Entry lookup returns an EntryId and resolves to EntryRef / EntryMut via Database::entry / entry_mut, avoiding lifetime issues with recursive GroupRef borrows.
  • Writes navigate and create groups segment-by-segment via group_mut / add_group, then update or add_entry.
  • Field writes use the new set_protected / set_unprotected helpers, preserving "Password protected, everything else unprotected" behavior.

Lookup semantics are unchanged: path segments before the last name exact-named subgroups; the final segment is searched recursively by entry title.

Changed

Migrate from keyring v3 to keyring-core v1 (#493) -- @jdx

Upstream keyring v4 is now just a CLI/sample app — the library moved to keyring-core plus per-platform credential-store crates. fnox now depends on:

  • apple-native-keyring-store (with the keychain feature) on macOS
  • windows-native-keyring-store on Windows
  • dbus-secret-service-keyring-store (with crypto-rust + vendored) on Linux

A new fnox_core::keyring_store::init() lazily registers the default store once per process; both the keychain provider and the github_oauth lease backend invoke it before constructing an Entry. Error classification now matches keyring_core::Error::{NoEntry, NoStorageAccess(_)} enum variants instead of string-matching error messages, producing more accurate ProviderSecretNotFound vs ProviderAuthFailed errors.

No config or CLI surface changes — the keychain provider and github_oauth lease backend keep the same TOML fields and behavior.

Documentation

Use the keychain for a bootstrap key, not bulk storage (#495) -- @jdx

The keychain provider docs now recommend storing a single age identity in the OS keychain and encrypting the rest of your secrets with the age provider, so you hit one "Always Allow" prompt instead of one per secret:

[providers]
keychain = { type = "keychain", service = "fnox" }
age      = { type = "age", recipients = ["age1..."], identity = { provider = "keychain", value = "age-key" } }

[secrets]
DATABASE_URL = { provider = "age", value = "encrypted..." }
API_KEY      = { provider = "age", value = "encrypted..." }
STRIPE_KEY   = { provider = "age", value = "encrypted..." }

Reach for provider = "keychain" directly only for the handful of bootstrap secrets that have nothing else to decrypt them.

Full Changelog: v1.25.0...v1.25.1

💚 Sponsor fnox

fnox is maintained by @jdx under en.dev — a small independent studio building developer tooling like mise, aube, hk, and more. Keeping fnox secure, maintained, and free is funded by sponsors.

If fnox is handling secrets or config for you or your team, please consider sponsoring at en.dev. Sponsorships are what let fnox stay independent and the project keep moving.

Don't miss a new fnox release

NewReleases is sending notifications on new releases.