Summary
The plugin client (HandleCommonStanza in Age/Recipients/PluginRecipient.cs and Age/Recipients/PluginIdentity.cs) handles the msg, request-secret, and confirm interactive stanzas, but not request-public, which the age-plugin protocol defines. A request-public stanza currently falls through to default and is answered with unsupported, so any plugin that asks the user for a non-secret value fails.
Spec
Per the C2SP age-plugin spec:
(request-public; MESSAGE) - the plugin requires some public string from the user in order to progress.
- Response is
(ok; REQUESTED_PUBLIC) or (fail).
It appears in both the recipient (phase 2) and identity (phase 2) state machines, i.e. it should be handled in both PluginRecipient and PluginIdentity.
The reference implementation (FiloSottile/age plugin/client.go) handles it alongside request-secret, distinguishing the two by type:
case "request-secret", "request-public":
if c.RequestValue == nil {
return true, writeStanza(conn, "fail")
}
secret, err := c.RequestValue(name, string(s.Body), s.Type == "request-secret")
...
return true, writeStanzaWithBody(conn, "ok", []byte(secret))
Why this should be small
The plumbing already exists:
IPluginCallbacks.RequestValue(string prompt, bool secret) already carries the secret flag — request-public is just the secret: false case.
- The CLI callback (
AgeCommand.CliPluginCallbacks.RequestValue) already implements the non-secret path (echoed Console.ReadLine), so no UI work is needed.
Suggested change:
- Add
request-public to the existing no-callbacks guard so it replies fail when no callbacks are wired.
- Add a
case "request-public": that calls callbacks!.RequestValue(prompt, secret: false) and writes ok with the returned value.
- Add test coverage in
PluginTests.cs for both the recipient and identity paths (value returned, and fail when no callbacks).
Context
Spotted while fixing the confirm command (#6). Deferred from that PR to keep it scoped to the confirm bug; this is a pre-existing gap, not a regression.
Summary
The plugin client (
HandleCommonStanzainAge/Recipients/PluginRecipient.csandAge/Recipients/PluginIdentity.cs) handles themsg,request-secret, andconfirminteractive stanzas, but notrequest-public, which the age-plugin protocol defines. Arequest-publicstanza currently falls through todefaultand is answered withunsupported, so any plugin that asks the user for a non-secret value fails.Spec
Per the C2SP age-plugin spec:
It appears in both the recipient (phase 2) and identity (phase 2) state machines, i.e. it should be handled in both
PluginRecipientandPluginIdentity.The reference implementation (FiloSottile/age
plugin/client.go) handles it alongsiderequest-secret, distinguishing the two by type:Why this should be small
The plumbing already exists:
IPluginCallbacks.RequestValue(string prompt, bool secret)already carries thesecretflag —request-publicis just thesecret: falsecase.AgeCommand.CliPluginCallbacks.RequestValue) already implements the non-secret path (echoedConsole.ReadLine), so no UI work is needed.Suggested change:
request-publicto the existing no-callbacks guard so it repliesfailwhen no callbacks are wired.case "request-public":that callscallbacks!.RequestValue(prompt, secret: false)and writesokwith the returned value.PluginTests.csfor both the recipient and identity paths (value returned, andfailwhen no callbacks).Context
Spotted while fixing the
confirmcommand (#6). Deferred from that PR to keep it scoped to the confirm bug; this is a pre-existing gap, not a regression.