Secret store (hostlib)

The secret_store capability is a small, sync host primitive for storing per-application credentials in the operating system's native secret store, with a portable JSON file fallback for headless environments. It is registered automatically by harn_hostlib::install_default and exposes four builtins:

BuiltinReturns
hostlib_secret_store_get{account, key, value, backend}
hostlib_secret_store_set{account, key, backend}
hostlib_secret_store_delete{account, key, deleted, backend}
hostlib_secret_store_list{account, keys, backend}

value is nil when the key is absent. backend is one of "keychain", "wincred", or "file" so callers can surface backend status in diagnostics without re-deriving it.

Backend selection

The active backend is resolved on every call (selection is essentially free) so an env-var override takes effect without a process restart.

OSDefault backend
macOS / iOSApple Keychain (security-framework, generic password item)
WindowsCredential Manager (CredRead/CredWrite, generic type)
Linux and otherFile backend at $XDG_CONFIG_HOME/<account>/credentials.json

Forcing the file backend

Set HARN_SECRET_STORE_BACKEND=file to use the file backend regardless of OS. This is the right knob for sandboxed CI, eval harnesses, container images that must not touch the user's keychain, and anything else that needs deterministic, file-based storage.

Account namespacing

The account argument scopes every key to the calling application (burin, harn-cloud-admin, etc.) so two applications can use the same key name without collision:

  • Keychainaccount maps to kSecAttrService; key maps to kSecAttrAccount. Matches the layout used by the legacy Swift KeychainCredentialStore in burin-code so existing entries are reachable without migration.
  • Credential Manageraccount/key becomes the credential's target name (e.g. burin/OPENAI_API_KEY).
  • File backend — credentials land at $XDG_CONFIG_HOME/<account>/credentials.json (or %APPDATA%\<account>\credentials.json on Windows). The file is written with 0o600 on Unix; the containing directory with 0o700.

The path layout for the file backend is byte-compatible with burin-code's existing $XDG_CONFIG_HOME/burin/credentials.json, so existing deployments migrate without any data movement.

Scope and non-goals

The capability owns where the bytes live and nothing else. Audit logging, env-vs-stored precedence, schema validation beyond builtin signatures, and migration logic belong in the .harn orchestration layer that composes this primitive. Concretely:

  • No "convenience" helpers like effectiveValue(envKey, fallback) — the caller decides whether to read env, the secret store, or both.
  • No rotation, versioning, or zeroizing buffers. Those live in harn-vm::secrets (the async SecretProvider chain used by the LLM caller), which solves a different problem.
  • No hardware-backed key stores. Deferred to a follow-up if any consumer asks.

Verifying a setup

HARN_SECRET_STORE_BACKEND=file cargo test -p harn-hostlib --test secret_store

secret_store.rs (file backend) runs on every CI runner. secret_store_os_native.rs exercises the Keychain (macOS) and Credential Manager (Windows) backends end-to-end; on Linux it compiles down to nothing because there is no OS-native backend yet.

Open follow-up

  • Linux libsecret / secret-service backend. Implemented as a wrapper around the keyring crate's linux-native feature when a consumer needs it — harn-vm::secrets::KeyringSecretProvider already pulls the same crate, so the dependency is free. Headless Linux CI keeps using the file backend (the same fallback HARN_SECRET_STORE_BACKEND=file selects) so behavior is identical to today.