{"thread":{"id":"49450","subject":"[PATCH] Improvement to only call Git Credential Helper once","startedAt":"2018-09-28T16:37:38Z","lastAt":"2018-09-30T05:20:55Z","messageCount":6,"participants":["Kyle Hubert","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"359217","messageId":"20180928163716.29947-1-khubert@gmail.com","threadId":"49450","inReplyTo":null,"subject":"[PATCH] Improvement to only call Git Credential Helper once","fromName":"Kyle Hubert","fromEmail":"khubert@gmail.com","sentAt":"2018-09-28T16:37:16Z","receivedAt":"2018-09-28T16:37:38Z","isPatch":true,"sender":{"key":"khubert@gmail.com","avatar":"https://gravatar.com/avatar/84db3960dbec8e65d7b11a2708b965b25a242832a9f303604433aa066d742a90?d=mp&s=160"},"body":"When calling the Git Credential Helper that is set in the git config,\nthe get command can return a credential. Git immediately turns around\nand calls the store command, even though that credential was just\nretrieved by the Helper. This creates two side effects. First of all,\nif the Helper requires a passphrase, the user has to type it in\ntwice. Secondly, if the user has a number of helpers, this retrieves\nthe credential from one service and writes it to all services.\n\nThis commit introduces a new field in the credential struct that\ndetects when the credential was retrieved using the Helper, and early\nexits when called to store the credential.\n---\n credential.c | 8 +++++++-\n credential.h | 3 ++-\n 2 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/credential.c b/credential.c\nindex 62be651b0..79bf62d49 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -280,8 +280,10 @@ void credential_fill(struct credential *c)\n \n \tfor (i = 0; i < c->helpers.nr; i++) {\n \t\tcredential_do(c, c->helpers.items[i].string, \"get\");\n-\t\tif (c->username && c->password)\n+\t\tif (c->username && c->password) {\n+\t\t\tc->retrieved = 1;\n \t\t\treturn;\n+\t\t}\n \t\tif (c->quit)\n \t\t\tdie(\"credential helper '%s' told us to quit\",\n \t\t\t    c->helpers.items[i].string);\n@@ -300,6 +302,10 @@ void credential_approve(struct credential *c)\n \t\treturn;\n \tif (!c->username || !c->password)\n \t\treturn;\n+\tif (c->retrieved) {\n+\t\tc->approved = 1;\n+\t\treturn;\n+\t}\n \n \tcredential_apply_config(c);\n \ndiff --git a/credential.h b/credential.h\nindex 6b0cd16be..d99df2f52 100644\n--- a/credential.h\n+++ b/credential.h\n@@ -8,7 +8,8 @@ struct credential {\n \tunsigned approved:1,\n \t\t configured:1,\n \t\t quit:1,\n-\t\t use_http_path:1;\n+\t\t use_http_path:1,\n+\t\t retrieved:1;\n \n \tchar *username;\n \tchar *password;\n-- \n2.11.0\n\n"},{"id":"359240","messageId":"xmqqva6pbeud.fsf@gitster-ct.c.googlers.com","threadId":"49450","inReplyTo":"20180928163716.29947-1-khubert@gmail.com","subject":"Re: [PATCH] Improvement to only call Git Credential Helper once","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-09-28T19:29:30Z","receivedAt":"2018-09-28T19:29:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Kyle Hubert <khubert@gmail.com> writes:\n\n> Subject: Re: [PATCH] Improvement to only call Git Credential Helper once\n\nNobody will send in a patch to worsen things, so phrases like\n\"Improvement to\" that convey no useful information has no place on\nthe title.\n\nThere probably are multiple ways that credential helpers are not\ncalled just once and many of them probably are legit (e.g. \"get\" is\nnot the only request one helper can receive).  It is unclear why\n\"only call helper once\" is an improvement unless the reader reads\nmore, which means the title could be a lot more improved.\n\n\tSide note: this matters because in \"git shortlog --no-merges\"\n\tthe title is the only thing you can tell your readers what\n\tyour contribution was about.\n\n> When calling the Git Credential Helper that is set in the git config,\n> the get command can return a credential. Git immediately turns around\n> and calls the store command, even though that credential was just\n> retrieved by the Helper. \n\nGood summary of the current behaviour.  A paragraph break here would\nmake the result easier to read.\n\n> This creates two side effects. First of all,\n> if the Helper requires a passphrase, the user has to type it in\n> twice.\n\nHmph, because...?  If I am reading the flow correctly, an\napplication would\n\n - call credential_fill(), which returns when both username and\n   password are obtained by a \"get\" request to one of the helpers.\n\n - use the credential to authenticate with a service and find that\n   it was accepted.\n\n - call credential_approve(), which does \"store\" to all the helpers.\n\nWhere does the \"twice\" come from?\n\nAh, that is not between the application and the service, but between\nthe helper and the user you are required to \"unlock\" the helper?\n\nOK, that wish makes sense.\n\nIt does not make much sense to ask helper A for credential and then\ntell it to write it back the same thing.\n\nHOWEVER.  Let me play a devil's advocate.\n\nThe \"store\" does not have to necessarily mean \"write it back\", no?\n\nImagine a helper that is connected to an OTP password device that\ngives a different passcode every time button A is pressed, and there\nare two other buttons B to tell the device that the password was\naccepted and C to tell the device that the password was rejected\n(i.e. we are out of sync).  \"get\" would press button A and read the\noutput, \"store\" would press button B and \"erase\" would press button\nC, I would imagine.  With the current credential.c framework, you\ncan construct such a helper.  The proposed patch that stops calling\n\"store\" unconditionally makes it impossible to build.\n\n> Secondly, if the user has a number of helpers, this retrieves\n> the credential from one service and writes it to all services.\n\nIt is unclear why you think it is a bad thing.  You need to\nelaborate.\n\nOn the other hand, I can think of a case to illustrate why it is a\nbad idea to unconditionally stop calling \"store\" to other helpers.\nIf one helper is a read-only \"encrypted on disk\" one, you may want\nto require passphrase to \"decrypt\" to implement the \"get\" request to\nthe helper.  You would then overlay a \"stay only in-core for a short\ntime\" helper and give higher priority to it.\n\nBy doing so, on the first \"get\" request will ask the in-core one,\nwhich says \"I dunno\", then the encrypted-on-disk one interacts with\nthe end-user and gives the credential.  The current code \"store\"s to\nthe in-core one as well as the encrypted-on-disk one, and second and\nsubseqhent \"get\" request can be served from that in-core helper.\n\n\tSide note: and the \"store\" to encrypted-on-disk one may not\n\teven need passphrase, even if \"get\" from it may need one.\n\n\"We got the credential from some helper, so we won't call store\"\nmakes it impossible to build such an arrangement.\n\nThe above is a devil's advocate response in that I do not mean to\nsay that your proposed workaround does not solve *your* immediate\nneed, but to point out that you are closing many doors for needs\nother people would have, or needs they already satisfy by taking\nadvantage of the current behaviour the proposed patch is breaking.\n\nSo, I dunno.  I certainly do not think it is a bad idea to stop\nfeeding _other_ helpers.  I also do not think it is a good idea to\nunconditionally stop calling \"store\" to the same helper, but I can\nsee the benefit for having an option to skip \"store\" to the same\nhelper.  I am not sure if there should be an option to stop feeding\nother helpers.\n\nThanks.\n"},{"id":"359249","messageId":"CAJoZ4U3cLTJaX8ZJdmNN_xBLOVKXoy53F4n=Y76wYEoBsNWNcw@mail.gmail.com","threadId":"49450","inReplyTo":"xmqqva6pbeud.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH] Improvement to only call Git Credential Helper once","fromName":"Kyle Hubert","fromEmail":"khubert@gmail.com","sentAt":"2018-09-28T20:54:06Z","receivedAt":"2018-09-28T20:54:20Z","isPatch":true,"sender":{"key":"khubert@gmail.com","avatar":"https://gravatar.com/avatar/84db3960dbec8e65d7b11a2708b965b25a242832a9f303604433aa066d742a90?d=mp&s=160"},"body":"Thank you for the review, commenting inline.\n\nOn Fri, Sep 28, 2018 at 3:29 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Kyle Hubert <khubert@gmail.com> writes:\n>\n> > Subject: Re: [PATCH] Improvement to only call Git Credential Helper once\n>\n> Nobody will send in a patch to worsen things, so phrases like\n> \"Improvement to\" that convey no useful information has no place on\n> the title.\n>\n> There probably are multiple ways that credential helpers are not\n> called just once and many of them probably are legit (e.g. \"get\" is\n> not the only request one helper can receive).  It is unclear why\n> \"only call helper once\" is an improvement unless the reader reads\n> more, which means the title could be a lot more improved.\n>\n>         Side note: this matters because in \"git shortlog --no-merges\"\n>         the title is the only thing you can tell your readers what\n>         your contribution was about.\n\nYes, thank you. I just joined the mailing list. I'm getting a sense\nfor the style here. If this patch moves forward, I will rewrite the\ntitle.\n\n> > When calling the Git Credential Helper that is set in the git config,\n> > the get command can return a credential. Git immediately turns around\n> > and calls the store command, even though that credential was just\n> > retrieved by the Helper.\n>\n> Good summary of the current behaviour.  A paragraph break here would\n> make the result easier to read.\n\nCheck.\n\n> > This creates two side effects. First of all,\n> > if the Helper requires a passphrase, the user has to type it in\n> > twice.\n>\n> Hmph, because...?  If I am reading the flow correctly, an\n> application would\n>\n>  - call credential_fill(), which returns when both username and\n>    password are obtained by a \"get\" request to one of the helpers.\n>\n>  - use the credential to authenticate with a service and find that\n>    it was accepted.\n>\n>  - call credential_approve(), which does \"store\" to all the helpers.\n>\n> Where does the \"twice\" come from?\n>\n> Ah, that is not between the application and the service, but between\n> the helper and the user you are required to \"unlock\" the helper?\n>\n> OK, that wish makes sense.\n>\n> It does not make much sense to ask helper A for credential and then\n> tell it to write it back the same thing.\n>\n> HOWEVER.  Let me play a devil's advocate.\n>\n> The \"store\" does not have to necessarily mean \"write it back\", no?\n>\n> Imagine a helper that is connected to an OTP password device that\n> gives a different passcode every time button A is pressed, and there\n> are two other buttons B to tell the device that the password was\n> accepted and C to tell the device that the password was rejected\n> (i.e. we are out of sync).  \"get\" would press button A and read the\n> output, \"store\" would press button B and \"erase\" would press button\n> C, I would imagine.  With the current credential.c framework, you\n> can construct such a helper.  The proposed patch that stops calling\n> \"store\" unconditionally makes it impossible to build.\n\nThis example helper would require knowing external state between\nbutton pushes. What about aborting the operation? Regardless of the\nexample, I can understand your concern. If a helper depended on\nreceiving confirmation and rejection, this change would break that\nbehavior.\n\nThe patch is intended to address the common problem of a multi-user\nsystem running on a server in headless mode, in other words, without\nlibsecret available via DBus. As such, this patch could eliminate\nhaving to double type passwords for every git operation accessing the\nstored credential.\n\n> > Secondly, if the user has a number of helpers, this retrieves\n> > the credential from one service and writes it to all services.\n>\n> It is unclear why you think it is a bad thing.  You need to\n> elaborate.\n>\n> On the other hand, I can think of a case to illustrate why it is a\n> bad idea to unconditionally stop calling \"store\" to other helpers.\n> If one helper is a read-only \"encrypted on disk\" one, you may want\n> to require passphrase to \"decrypt\" to implement the \"get\" request to\n> the helper.  You would then overlay a \"stay only in-core for a short\n> time\" helper and give higher priority to it.\n>\n> By doing so, on the first \"get\" request will ask the in-core one,\n> which says \"I dunno\", then the encrypted-on-disk one interacts with\n> the end-user and gives the credential.  The current code \"store\"s to\n> the in-core one as well as the encrypted-on-disk one, and second and\n> subseqhent \"get\" request can be served from that in-core helper.\n>\n>         Side note: and the \"store\" to encrypted-on-disk one may not\n>         even need passphrase, even if \"get\" from it may need one.\n>\n> \"We got the credential from some helper, so we won't call store\"\n> makes it impossible to build such an arrangement.\n>\n> The above is a devil's advocate response in that I do not mean to\n> say that your proposed workaround does not solve *your* immediate\n> need, but to point out that you are closing many doors for needs\n> other people would have, or needs they already satisfy by taking\n> advantage of the current behaviour the proposed patch is breaking.\n\nThis seems very valid, I like the configuration your propose. The\nargument for not storing to all helpers is to prevent leaking\ncredentials across many key stores. In some cases, a developer can\nshort cut looking up the plain text credential, and instead copy the\nencrypted credential to a new server. This behavior can lead to\ncopying to a local development machine which is not headless, and does\nhave an OS keychain available. If allowed to write the secret\ncredential to all available helpers, this will leak the credential to\nthe developers local machine.\n\n> So, I dunno.  I certainly do not think it is a bad idea to stop\n> feeding _other_ helpers.  I also do not think it is a good idea to\n> unconditionally stop calling \"store\" to the same helper, but I can\n> see the benefit for having an option to skip \"store\" to the same\n> helper.  I am not sure if there should be an option to stop feeding\n> other helpers.\n\nI understand your thinking. Is there code you can point me to that\nwould allow referencing an option to specific helpers? Maybe it could\nbe reasoned that configuration could dictate not leaking credentials,\nand retrieval doesn't need confirmation.\n\nThank you for your review,\n-Kyle\n"},{"id":"359281","messageId":"20180929081705.GI2174@sigill.intra.peff.net","threadId":"49450","inReplyTo":"20180928163716.29947-1-khubert@gmail.com","subject":"Re: [PATCH] Improvement to only call Git Credential Helper once","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-09-29T08:17:05Z","receivedAt":"2018-09-29T08:17:09Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Sep 28, 2018 at 12:37:16PM -0400, Kyle Hubert wrote:\n\n> When calling the Git Credential Helper that is set in the git config,\n> the get command can return a credential. Git immediately turns around\n> and calls the store command, even though that credential was just\n> retrieved by the Helper. This creates two side effects. First of all,\n> if the Helper requires a passphrase, the user has to type it in\n> twice. Secondly, if the user has a number of helpers, this retrieves\n> the credential from one service and writes it to all services.\n> \n> This commit introduces a new field in the credential struct that\n> detects when the credential was retrieved using the Helper, and early\n> exits when called to store the credential.\n\nWow, what's old is new again. Here's more or less the same patch from\n2012:\n\n  https://public-inbox.org/git/20120407033417.GA13914@sigill.intra.peff.net/\n\nUnfortunately, some people seem to rely on this multi-helper behavior. I\nrecommend reading the whole thread, as there are some interesting bits\nin it (that I had always meant to return to, but somehow 6 years went\nby).\n\nI'm not entirely opposed to breaking the current behavior in the name of\nbetter security (namely not unexpectedly propagating credentials), but\nit would be nice if we provided better tools for people to let their\nhelpers interact (like the credential-wrap thing I showed in that\nthread).\n\n> ---\n>  credential.c | 8 +++++++-\n>  credential.h | 3 ++-\n>  2 files changed, 9 insertions(+), 2 deletions(-)\n\nI know your patch is right, because it's almost identical to mine. :)\n(Mine didn't use the \"retrieved\" flag in the middle, but just set\n\"approved\" directly).\n\nIf we do go this route, though, we might want to steal the test from\nthat earlier round.\n\n-Peff\n"},{"id":"359309","messageId":"xmqqk1n486o1.fsf@gitster-ct.c.googlers.com","threadId":"49450","inReplyTo":"20180929081705.GI2174@sigill.intra.peff.net","subject":"Re: [PATCH] Improvement to only call Git Credential Helper once","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-09-29T19:06:38Z","receivedAt":"2018-09-29T19:08:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Wow, what's old is new again. Here's more or less the same patch from\n> 2012:\n>\n>   https://public-inbox.org/git/20120407033417.GA13914@sigill.intra.peff.net/\n>\n> Unfortunately, some people seem to rely on this multi-helper behavior. I\n> recommend reading the whole thread, as there are some interesting bits\n> in it (that I had always meant to return to, but somehow 6 years went\n> by).\n\nAfter reading that thread again, my version of summary is that\n\n - storing the credential obtained from a helper back to the same\n   helper may be pointless at best and may incur end-user\n   interaction (e.g. asking for symmetric encryption key before the\n   data hits the disk), but it can be used as a \"we used what you\n   gave us successfully\" ping-back signal.  We may not have designed\n   approve() to do \"store\" back to the same helper by default and\n   instead to do so only when asked by the helper, if we knew\n   better.  But an unconditional change of behaviour will break\n   existing users and helpers.\n\n - storing the credential obtained from a helper to a different\n   helper may have security implications, and we may not have\n   designed approve() to do \"store\" by default to all helpers if we\n   knew better.  But an unconditional change of behaviour will break\n   existing users and helpers.\n\nAssuming that the above summary is accurate, I think the former is\neasier to solve.  In the ideal end game state, we would also have\n\"accepted\" in the protocol and call the helper that gave us the\naccepted credential material with an earlier \"get\" (if the helper is\nupdated to understand \"accepted\").  The \"accepted\" may not even need\nto receive the credential material as the payload.\n\nThe latter is trickier, as there are design considerations.\n\n - We may want to allow the helper that gives the credential back\n   from the outside world upon \"get\" request to say \"you can 'store'\n   this to other helpers of this kind but not of that kind\".  If we\n   want to do so, we'd need to extend what is returned from the\n   helper upon \"get\" (e.g. \"get2\" will give more than what \"get\"\n   does back).\n\n - We may want to allow the helper that receives the credential\n   material from others to behave differently depending on where it\n   came from, and what other helpers done to it (e.g. even a new\n   credential the end user just typed may not want to go to all\n   helpers; an on-disk helper may feel \"I'd take it if the only\n   other helpers that responded to 'store' are the transient\n   'in-core' kind, but otherwise I'd ignore\").  I am not offhand\n   sure what kind of flexibility and protocol extension is\n   necessary.\n\n - We may want to be able to override the preference the helper\n   makes in the above two.  The user may want to say \"Only use this\n   on-disk helper as a read-only source and never call 'store' on it\n   to add new entries (or update an existing one).\"\n\n"},{"id":"359331","messageId":"20180930052051.GC32120@sigill.intra.peff.net","threadId":"49450","inReplyTo":"xmqqk1n486o1.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH] Improvement to only call Git Credential Helper once","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-09-30T05:20:51Z","receivedAt":"2018-09-30T05:20:55Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Sep 29, 2018 at 12:06:38PM -0700, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > Wow, what's old is new again. Here's more or less the same patch from\n> > 2012:\n> >\n> >   https://public-inbox.org/git/20120407033417.GA13914@sigill.intra.peff.net/\n> >\n> > Unfortunately, some people seem to rely on this multi-helper behavior. I\n> > recommend reading the whole thread, as there are some interesting bits\n> > in it (that I had always meant to return to, but somehow 6 years went\n> > by).\n> \n> After reading that thread again, my version of summary is that\n> \n>  - storing the credential obtained from a helper back to the same\n>    helper may be pointless at best and may incur end-user\n>    interaction (e.g. asking for symmetric encryption key before the\n>    data hits the disk), but it can be used as a \"we used what you\n>    gave us successfully\" ping-back signal.  We may not have designed\n>    approve() to do \"store\" back to the same helper by default and\n>    instead to do so only when asked by the helper, if we knew\n>    better.  But an unconditional change of behaviour will break\n>    existing users and helpers.\n> \n>  - storing the credential obtained from a helper to a different\n>    helper may have security implications, and we may not have\n>    designed approve() to do \"store\" by default to all helpers if we\n>    knew better.  But an unconditional change of behaviour will break\n>    existing users and helpers.\n\nYeah, I agree with that summary, except I want to pick a nit with the\nvery last sentence.\n\nYou're not wrong that it will break those existing users, but there are\nsecurity implications. If you're expecting and relying on that behavior,\nthen all is well with the current code, and you'd be annoyed by a\nchange. But if you're not expecting it, the system is not just broken:\nit may be leaking credentials from a higher-security first-choice helper\nto a lower-security second-choice one.\n\nAnd that's why I wonder if it might be the right thing to break\ncompatibility in this case.\n\n> Assuming that the above summary is accurate, I think the former is\n> easier to solve.  In the ideal end game state, we would also have\n> \"accepted\" in the protocol and call the helper that gave us the\n> accepted credential material with an earlier \"get\" (if the helper is\n> updated to understand \"accepted\").  The \"accepted\" may not even need\n> to receive the credential material as the payload.\n\nI think you can really solve both by adding a \"retrieved\" key to the\nstore step of the protocol (which can be done in a backwards-compatible\nway, since all sides are supposed to ignore keys they don't understand).\n\nThen a helper which understands \"retrieved\" can say \"Oh, this came from\nanother helper; I don't need to store it\". And that includes things that\nmight have come from itself! Likewise, helpers might take options to\ntell them how to behave. So the \"do some https thing and cache it\"\ncombination from that earlier thread would be:\n\n  [credential]\n  helper = magic-https-thing\n  helper = cache\n\nand the safe version of \"try high-security thing, and then fall back to\ncaching\" would be:\n\n  [credential]\n  helper = high-security-thing\n  helper = cache --no-retrieved\n\n(that keeps the default matching the current behavior, but obviously we\ncould flip it if we wanted to have a safer default but leave the\nexisting case with an escape hatch).\n\n> The latter is trickier, as there are design considerations.\n> \n>  - We may want to allow the helper that gives the credential back\n>    from the outside world upon \"get\" request to say \"you can 'store'\n>    this to other helpers of this kind but not of that kind\".  If we\n>    want to do so, we'd need to extend what is returned from the\n>    helper upon \"get\" (e.g. \"get2\" will give more than what \"get\"\n>    does back).\n\nI don't think you need a \"get2\". The helpers should be free to pass back\nextra keys (but old versions of Git just won't do anything with them).\n\n>  - We may want to allow the helper that receives the credential\n>    material from others to behave differently depending on where it\n>    came from, and what other helpers done to it (e.g. even a new\n>    credential the end user just typed may not want to go to all\n>    helpers; an on-disk helper may feel \"I'd take it if the only\n>    other helpers that responded to 'store' are the transient\n>    'in-core' kind, but otherwise I'd ignore\").  I am not offhand\n>    sure what kind of flexibility and protocol extension is\n>    necessary.\n\nIf we have a \"retrieved\" flag, it could be \"retrieved=some-helper\" to\nshow which helper it came from. One tricky thing, though, is that\nhelpers do not always have a simple name (they can be arbitrary shell\nexpressions).\n\n>  - We may want to be able to override the preference the helper\n>    makes in the above two.  The user may want to say \"Only use this\n>    on-disk helper as a read-only source and never call 'store' on it\n>    to add new entries (or update an existing one).\"\n\nYes. I think if we go this route that helpers would just annotate\ncredentials they pass back (e.g., a key that says \"this credential has\nproperty foo\") and other credentials would take user direction to\nrespect them (e.g., credential-cache would grow an option to say \"do not\ncache things with property foo\").\n\nThe final patch in that earlier thread:\n\n  https://public-inbox.org/git/20120408071300.GB13662@sigill.intra.peff.net/\n\nshowed what it would look like to just pass the properties around\nbetween the helpers. That would be enough so that an individual helper\n\"foo\" could:\n\n  - set a key like \"from=foo\"\n\n  - treat \"from=foo\" as a no-op for the \"store\" step (thus avoiding\n    the ask-for-passphrase-again problem that started this current thread)\n\nAnd then other helpers like credential-cache could grow options like\n\"--ignore=from\", which would not cache anything that has a non-empty\n\"from\" property.\n\n-Peff\n"}]}