Severity: medium — an unattended CLI hangs with no output and no way to recover. Verified: reproduced against KWallet's shim.
Found while validating #19.
What happens
All four libsecret entry points are invoked with a NULL GCancellable:
LibsecretCredentialManager.cs:549 secret_password_storev_sync(..., IntPtr.Zero, out var error)
LibsecretCredentialManager.cs:574 secret_password_lookupv_sync(..., IntPtr.Zero, out var error)
LibsecretCredentialManager.cs:617 secret_password_clearv_sync(..., IntPtr.Zero, out var error)
LibsecretCredentialManager.cs:633 secret_password_searchv_sync(..., IntPtr.Zero, out var error)
Prompting is part of the Secret Service contract — an implementation may need to unlock a collection before serving a request. When it does, these synchronous calls block until the prompt is answered. With no GUI there is nothing to answer it, so they block indefinitely: no timeout, no cancellation, no output.
Reproduction
Against KWallet's ksecretd on the default collection, a single AddCredentialAsync never returns. ksecretd logs:
kf.i18n: 0 instead of 1 arguments to message "<html><head/><body><..." supplied before conversion
Using kwallet without parent window!
— it is trying to raise a wallet-unlock dialog. The probe had to be killed at 120s.
GNOME Keyring does not expose this today because CI and the dev box run it already-unlocked, but the same path exists there: a locked login keyring will prompt.
Why it matters beyond KWallet
The public API is Task-returning and ImportCredentialsCommand/ExportCredentialsCommand already thread a CancellationToken from Spectre.Console.Cli. A caller who cancels gets no cancellation at all once execution is inside one of these calls, because the token never reaches libsecret.
Fix sketch
Create a GCancellable per operation, wire it to the caller's CancellationToken (g_cancellable_cancel on token registration), and pass it instead of IntPtr.Zero. ICredentialManager's methods would need to accept a CancellationToken to carry it end to end, which is a breaking interface change — so either that, or a bounded internal timeout as a smaller non-breaking step.
Worth deciding which before starting: the interface change is a major-version item, the timeout is not.
Severity: medium — an unattended CLI hangs with no output and no way to recover. Verified: reproduced against KWallet's shim.
Found while validating #19.
What happens
All four libsecret entry points are invoked with a NULL
GCancellable:Prompting is part of the Secret Service contract — an implementation may need to unlock a collection before serving a request. When it does, these synchronous calls block until the prompt is answered. With no GUI there is nothing to answer it, so they block indefinitely: no timeout, no cancellation, no output.
Reproduction
Against KWallet's
ksecretdon the default collection, a singleAddCredentialAsyncnever returns.ksecretdlogs:— it is trying to raise a wallet-unlock dialog. The probe had to be killed at 120s.
GNOME Keyring does not expose this today because CI and the dev box run it already-unlocked, but the same path exists there: a locked login keyring will prompt.
Why it matters beyond KWallet
The public API is
Task-returning andImportCredentialsCommand/ExportCredentialsCommandalready thread aCancellationTokenfrom Spectre.Console.Cli. A caller who cancels gets no cancellation at all once execution is inside one of these calls, because the token never reaches libsecret.Fix sketch
Create a
GCancellableper operation, wire it to the caller'sCancellationToken(g_cancellable_cancelon token registration), and pass it instead ofIntPtr.Zero.ICredentialManager's methods would need to accept aCancellationTokento carry it end to end, which is a breaking interface change — so either that, or a bounded internal timeout as a smaller non-breaking step.Worth deciding which before starting: the interface change is a major-version item, the timeout is not.