mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 08:27:04 +00:00
`SecureKeyStorage`'s three keyring paths (save/get/delete) each called `Keyring.create()` on every invocation. Each call opens a fresh backend session: - macOS: a new Security Framework session against `login.keychain`. Depending on the user's keychain policy (short access window, ACL on the amethyst-desktop item, or first-touch after unlock timeout), this surfaces a Keychain Access prompt every time. - Linux Secret Service / KWallet: a fresh session may re-trigger the wallet-unlock prompt if the daemon closed the previous session. - Windows Credential Manager: less user-visible but still redundant. Amethyst's cold-boot touches the store at least twice — once for `DesktopAccountStorage`'s AES-256-GCM metadata key (`account-metadata-key`), then again for the active account's nsec — so the user was seeing the OS keychain unlock prompt twice in a row before the UI was reachable. Fix: memoise the `Keyring` handle for the lifetime of the process. The `Keyring` object is thread-safe for the three ops we call, so a double-checked lazy singleton behind `keyringLock` is sufficient. The NPE hit path is a proper lazy: any `BackendNotSupportedException` bubbles up on the first call and is caught by the existing outer try/catch, which flips `keyringAvailable=false` and falls back to the encrypted file path (unchanged). Includes a small package-private `KeyringHandle` interface + real delegator so `SecureKeyStorageKeyringCacheTest` can substitute an in-memory handle and count backend-open invocations without touching the OS keychain. Three cases: 1. Cold-boot storm (save/get/delete across metadata + account keys) opens the Keyring exactly once. 2. Repeated `hasPrivateKey` reuses the cache. 3. Concurrent first-touches from 16 threads still open the Keyring exactly once (double-checked locking is race-free). No behavioural change beyond the prompt-count fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>