This code makes a call with suffix .Prompt, then registers a MatchSignal for the successful completion of the prompt, at which point it blocks until it receives the signal with name suffix .Completed. Once it receives the signal, unlocking can proceed and secrets can be interacted with.
I believe there is an issue here that the signal handling is registered after the .Prompt call has been made. This leaves a short window for the .Completed response to appear on the dbus before we have registered the handler. Typically, with the user filling in a password this would not be an issue, but if the user has set up automatic unlocking, it appears that the response can come fast enough to trigger a race.
Suggested fix
Naively, I think this is as simple as delaying the .Prompt call until right before we block awaiting the signal. I've opened a PR to demonstrate this: https://github.com/zalando/go-keyring/pull/105
I can't say for sure that it hasn't had some other unintended side effect but we've had three reports that it has resolved the original issue.
More details
In this collapsed section you can find the dbus-monitor log showing two attempts to use gh in a way that accessed the secret service, right after machine restart. Below the details section I will detail the specific lines of interest.
Description
Hello, I am from the GitHub CLI team and we use this library to store user tokens in secret storage. Historically we've had some issues around the CLI hanging when trying to interact with a secret service and recently we had a number of reports about the CLI failing to use the secret service on the first attempt after machine restart on linux.
After some investigation I believe the culprit is a race condition in how this library handles prompts. I'll explain the investigation in further detail below but the specific issue is in the following code, which runs when an attempt to
Unlock
results in a prompt: https://github.com/zalando/go-keyring/blob/b0e756d4fd2ce0775d2482c891194fe34cacfbfa/secret_service/secret_service.go#L187-L219This code makes a call with suffix
.Prompt
, then registers aMatchSignal
for the successful completion of the prompt, at which point it blocks until it receives the signal with name suffix.Completed
. Once it receives the signal, unlocking can proceed and secrets can be interacted with.I believe there is an issue here that the signal handling is registered after the
.Prompt
call has been made. This leaves a short window for the.Completed
response to appear on the dbus before we have registered the handler. Typically, with the user filling in a password this would not be an issue, but if the user has set up automatic unlocking, it appears that the response can come fast enough to trigger a race.Suggested fix
Naively, I think this is as simple as delaying the
.Prompt
call until right before we block awaiting the signal. I've opened a PR to demonstrate this: https://github.com/zalando/go-keyring/pull/105I can't say for sure that it hasn't had some other unintended side effect but we've had three reports that it has resolved the original issue.
More details
In this collapsed section you can find the
dbus-monitor
log showing two attempts to usegh
in a way that accessed the secret service, right after machine restart. Below the details section I will detail the specific lines of interest.In the first usage of
gh
we see the call to.Unlock
returning with a need to prompt:Immediately after this we see the call to
.Prompt
:Then shortly after we see the
.Completed
response:And following that we see the attempt to
AddMatch
on this interface:3 seconds later (which is the length of time the CLI waits before reporting a timeout), we see the client close:
In the second attempt to use
gh
, there is no prompt required and everything completes as expected: