Skip to content

fix(keychain): unlock the item on relock, not just the collection - #630

Merged
Benehiko merged 3 commits into
docker:mainfrom
rcjsuen:linux-indiviudal-lock-fix
Aug 20, 2026
Merged

fix(keychain): unlock the item on relock, not just the collection#630
Benehiko merged 3 commits into
docker:mainfrom
rcjsuen:linux-indiviudal-lock-fix

Conversation

@rcjsuen

@rcjsuen rcjsuen commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes docker/sbx-releases#340: saving credentials: failed to set item secret: org.freedesktop.Secret.Error.IsLocked on Linux Secret Service backends that lock an item independently of its collection — e.g. KeePassXC, whose per-item access confirmation (on by default) reports every item locked until the client is allowed, even while the collection is unlocked.

Two bugs, both required for recovery:

  1. Relock retries only unlocked the collection. The spec lets Service.Unlock take item paths, but withRelockRetry never passed one, so an independently locked item stayed locked and the retried SetItemSecret/DeleteItem/GetSecret failed forever. Delete, Get, Save, and loadSecret now pass the item's path so it is unlocked alongside the collection (CreateItem unchanged — no item exists yet).
  2. Prompt completion signals were never subscribed. NewService registered the invalid match rule org.freedesktop.Secret.Prompt='Completed' (a bare key=value pair) and discarded the bus's rejection, so PromptAndWait timed out on every real prompt even after the user confirmed it — and unlocking an item on KeePassXC raises exactly such a prompt, so fix 1 alone still failed. Now subscribes via WithMatchInterface + WithMatchMember and fails NewService if the subscription is rejected. Never surfaced on gnome-keyring: passwordless keyrings answer Unlock with the null prompt.

Validation

  • Unit: the relock retry tests assert the retry's Unlock includes the item path (fail without fix 1).
  • Live gnome-keyring: DOCKER_TARGET=ubuntu-24-gnome-keyring make keychain-linux-ci-unit-tests passes — no regression (gnome-keyring locks at collection granularity only).
  • Live KeePassXC (Ubuntu 24.04 VM, ConfirmAccessItem on): reproduced the reported error byte-for-byte; Unlock([collection]) alone never recovers; with both fixes, Unlock([collection, item]) raises the access prompt and the retried write succeeds once allowed. With only fix 1 it still failed — 30s timeout waiting for a Prompt.Completed signal that was never subscribed.

🤖 Generated with Claude Code

rcjsuen and others added 3 commits August 20, 2026 09:52
…st the collection

Saving or updating a credential could fail permanently with
`org.freedesktop.Secret.Error.IsLocked` on Linux backends that lock an
item independently of its enclosing collection. `Service.Unlock` accepts
both collection and item object paths, but every retry in
`withRelockRetry` (and its callers in Delete/Get/Save/loadSecret) only
ever unlocked the collection, so an independently locked item stayed
locked forever and the retried SetItemSecret/DeleteItem/GetSecret call
kept failing with the same error.

Thread the relevant item's object path into withRelockRetry wherever one
is already known (Delete, Get, Save's SetItemSecret and duplicate
DeleteItem, loadSecret's GetSecret) so the retry unlocks the item
alongside its collection.

Signed-off-by: Remy Suen <remy.suen@docker.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
NewService added its signal match rule as a bare key=value pair with the
interface name as the key. That is not a valid match rule, the bus
rejects it, and the error was discarded — so the connection never
subscribed to org.freedesktop.Secret.Prompt.Completed and PromptAndWait
timed out on every real (non-null) prompt, even after the user confirmed
it. The passwordless gnome-keyring test environment only ever produces
null prompts, which is why this never surfaced there.

Use interface and member match options instead, and fail NewService if
the subscription cannot be established.

Verified against KeePassXC's secret service with per-item access
confirmation enabled: unlocking an independently locked item now
completes once the access prompt is allowed, where it previously timed
out after 30 seconds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
Trim the itemPaths documentation to its contract and drop the test
comments and assertion prose that restated it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
@Benehiko
Benehiko merged commit fa04a23 into docker:main Aug 20, 2026
15 checks passed
@rcjsuen
rcjsuen deleted the linux-indiviudal-lock-fix branch August 20, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unlock() only targets the collection, not the item, breaking per-entry confirmation

3 participants