feat(keychain): classify locked collections with ErrCollectionLocked and bound the unlock prompt - #637
Draft
Benehiko wants to merge 8 commits into
Draft
feat(keychain): classify locked collections with ErrCollectionLocked and bound the unlock prompt#637Benehiko wants to merge 8 commits into
Benehiko wants to merge 8 commits into
Conversation
…and bound the unlock prompt
On headless Linux hosts (SSH key-only logins), PAM has no password to
auto-unlock the login keyring with, so the Secret Service collection comes
up locked after every keyring-daemon restart. The store's only reaction to
a locked collection was Service.Unlock -> PromptAndWait, whose failures
surfaced as opaque strings ("failed to prompt: prompt dismissed" /
"prompt timed out") that callers cannot classify, and whose wait was a
hardcoded 30s regardless of the caller's deadline.
- Export ErrCollectionLocked in the cross-platform keychain.go (mirroring
ErrKeychainUnavailable / ErrNoDefaultCollection; Linux-only match). Every
path that fails because the collection is locked and could not be
unlocked now wraps it, naming the collection and preserving the prompt
failure as the cause: the up-front unlock (new ensureCollectionUnlocked
helper, replacing five duplicated blocks), the re-unlock inside
withRelockRetry, and a collection still locked after the bounded retries.
- Bound the prompt wait by the operation's context: PromptAndWait (and the
prompt-capable Unlock/LockItems/CreateItem/DeleteItem) now take a ctx.
Store operations pass their original ctx - deliberately not the
context.WithoutCancel connection ctx - so a caller deadline bounds the
human-wait while in-flight D-Bus calls stay protected from teardown. The
internal 30s cap remains as an upper bound and is now created once
outside the receive loop, so unrelated bus signals no longer reset it.
A null prompt still returns before the ctx check, keeping best-effort
cleanup calls working on passwordless keyrings.
- Add fake-seam tests for every operation against a locked collection, and
a new ubuntu-24-gnome-keyring-locked CI target that runs an env-gated
live test against a password-protected keyring (created PAM-style via
gnome-keyring-daemon --login) with the collection locked, asserting the
operation fails fast with ErrCollectionLocked.
Validated live on an Ubuntu 24.04 VM: headless locked operations fail in
~15ms with the classified error (gnome-keyring dismisses the prompt
immediately when no prompter can be shown), an interactive unlock prompt
on a display still completes, and a 2s caller deadline aborts an
unanswered prompt at 2s. Deliberately not added (see the decision log):
prompter-presence probes, password callbacks, programmatic
master-password unlock, and library TTY prompting - the caller owns
remediation, and a locked collection must not be treated as "unavailable,
fall back", which would split credentials across stores.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…efault Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On headless Linux hosts with SSH key-only logins, PAM has no password to auto-unlock the login keyring, so the Secret Service collection is locked after every keyring-daemon restart. A downstream consumer hit this in production. Operations on a locked collection failed with opaque strings ("failed to prompt: prompt dismissed", "prompt timed out") that cannot be matched with
errors.Is, so nothing could tell the user their keychain is locked. The prompt wait was also a hardcoded 30 seconds, ignoring the caller's deadline.Changes
Classification:
keychain.ErrCollectionLocked, declared in the cross-platformkeychain.golike the existing sentinels (Linux-only match).ensureCollectionUnlockedhelper, replacing five duplicated blocks), the re-unlock insidewithRelockRetry, and a collection still locked after the bounded retries. The underlying prompt failure is kept as the cause.Bounded prompt wait:
PromptAndWait,Unlock,LockItems,CreateItemandDeleteItemnow take a context. Store operations pass their original operation context, so a caller deadline bounds the wait for the user. The internal 30s cap remains as an upper bound and is now created once outside the receive loop; previously any unrelated bus signal reset it.Deliberately not added (see the decision log): prompter detection, password callbacks, master-password unlock, and TTY prompting in the library. The caller owns the remediation message.
Tests
ErrCollectionLockedwith the collection path and the preserved cause, plus unlock-succeeds and retry-loop cases.ubuntu-24-gnome-keyring-lockedCI target: creates a password-protected keyring withgnome-keyring-daemon --login, locks the collection, and runs the gatedTestKeychainLiveLockedCollection. Fails fast in about 60ms in the container.Live validation (Ubuntu 24.04 VM, gnome-keyring)
The deadline test used a deliberately short 2 second context to prove propagation; it aborted the unanswered prompt at 2 seconds. Callers without a deadline still get the 30 second cap, and gnome-keyring itself never times a prompt out.
gnome-keyring dismisses the prompt immediately when no prompter can start, so the library never hung on a locked collection. The failure was fast but unclassifiable. This change makes it detectable and puts the unanswered-dialog case under the caller's control.
🤖 Generated with Claude Code