From pabs3 at bonedaddy.net Thu Sep 4 03:35:58 2025 From: pabs3 at bonedaddy.net (Paul Wise) Date: Thu, 04 Sep 2025 11:35:58 +0800 Subject: pass_secret_service: add to the passwordstore.org website? Message-ID: <99aaf76d37afd8b1ac10438e4fac718e54e23338.camel@bonedaddy.net> Hi all, [Please CC me on any replies] I wonder if pass_secret_service should be added to the pass website? https://github.com/mdellweg/pass_secret_service/ https://www.passwordstore.org/#other It adds a Linux Secret Service backend that uses pass to store secrets. https://specifications.freedesktop.org/secret-service-spec/latest/ -- bye, pabs https://bonedaddy.net/pabs3/ -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 833 bytes Desc: This is a digitally signed message part URL: From redxef at disroot.org Thu Sep 4 15:24:05 2025 From: redxef at disroot.org (Manuel Federanko) Date: Thu, 04 Sep 2025 17:24:05 +0200 Subject: pass grep doesn't abort if decryption fails Message-ID: Hi! I'm encountering an issue where decryption during pass grep never aborts if the decryption itself fails - continuing to try to grep. Specifically, it's related to the use-case described in https://codeberg.org/PassFF/passff/issues/629. I have a patch for this, though I'm not sure if the current behavior is deliberate. I'll send the patch shortly. --- Manuel Federanko <[redxef at disroot.org](mailto:redxef at disroot.org)> PGP: [A025FAA406747079C3888ADF0295A523B6BA74FE](https://keys.openpgp.org/vks/v1/by-fingerprint/A025FAA406747079C3888ADF0295A523B6BA74FE) [redxef.at](https://redxef.at) -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 833 bytes Desc: This is a digitally signed message part URL: From redxef at disroot.org Thu Sep 4 15:27:47 2025 From: redxef at disroot.org (redxef at disroot.org) Date: Thu, 4 Sep 2025 17:27:47 +0200 Subject: [PATCH] fix(grep): don't continue to grep if decryption fails. Message-ID: <20250904152746.34810-2-redxef@disroot.org> From: redxef This allows cancelling the grep process by cancelling the decryption of a file. A use-case might be graphical pinentry programs and the user wants to abort the process. Previous to this every abort of the pinentry would just continue to the next item. --- src/password-store.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/password-store.sh b/src/password-store.sh index 22e818f..f9cc4c5 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -419,9 +419,11 @@ cmd_find() { cmd_grep() { [[ $# -lt 1 ]] && die "Usage: $PROGRAM $COMMAND [GREPOPTIONS] search-string" - local passfile grepresults + local passfile decryptresult grepresults while read -r -d "" passfile; do - grepresults="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | grep --color=always "$@")" + decryptresult="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" + [[ $? -ne 0 ]] && die "Error: failed to decrypt" + grepresults="$($BASE64 -d <<<"$decryptresult" | grep --color=always "$@" )" [[ $? -ne 0 ]] && continue passfile="${passfile%.gpg}" passfile="${passfile#$PREFIX/}" -- 2.51.0 From rutherther at ditigal.xyz Thu Sep 4 17:28:24 2025 From: rutherther at ditigal.xyz (Rutherther) Date: Thu, 04 Sep 2025 19:28:24 +0200 Subject: [PATCH] fix(grep): don't continue to grep if decryption fails. In-Reply-To: <20250904152746.34810-2-redxef@disroot.org> References: <20250904152746.34810-2-redxef@disroot.org> Message-ID: <877bye9lfr.fsf@ditigal.xyz> redxef at disroot.org writes: > From: redxef > > This allows cancelling the grep process by cancelling the decryption > of a file. A use-case might be graphical pinentry programs and the user > wants to abort the process. Previous to this every abort of the pinentry > would just continue to the next item. Hi, I am thinking this could break a use case where someone has a password store with different keys used for decryption, while missing a key. Then they wouldn't be able to grep if those files are hit first, isn't that so? I don't really see a simple fix for that, other than somehow grouping the files to different keys, even that is complicated, as the files may be encrypted with several keys etc. But not sure if there is actually someone with such use case. Rutherther From redxef at disroot.org Fri Sep 5 12:07:50 2025 From: redxef at disroot.org (Manuel Federanko) Date: Fri, 05 Sep 2025 14:07:50 +0200 Subject: [PATCH] fix(grep): don't continue to grep if decryption fails. In-Reply-To: <877bye9lfr.fsf@ditigal.xyz> References: <20250904152746.34810-2-redxef@disroot.org> <877bye9lfr.fsf@ditigal.xyz> Message-ID: > Hi, I am thinking this could break a use case where someone has a > password store with different keys used for decryption, while missing > a key. Then they wouldn't be able to grep if those files are hit first, > isn't that so? Yes that would break such a setup. Maybe it's possible check if at least one of the keys used to encrypt the file is present in the key store with something like `gpg --list-keys gpg-ids...` and skip the entry on a non-zero exit code: ``` set_gpg_recipients "$passfile" $GPG "${GPG_OPTS[@]}" --list-keys "${GPG_RECIPIENTS[@]}" >/dev/null 2>&1 || continue ``` Cheers --- Manuel Federanko <[redxef at disroot.org](mailto:redxef at disroot.org)> PGP: [A025FAA406747079C3888ADF0295A523B6BA74FE](https://keys.openpgp.org/vks/v1/by-fingerprint/A025FAA406747079C3888ADF0295A523B6BA74FE) [redxef.at](https://redxef.at) -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 833 bytes Desc: This is a digitally signed message part URL: From tristan at logological.org Fri Sep 5 23:19:35 2025 From: tristan at logological.org (Tristan Miller) Date: Fri, 5 Sep 2025 18:19:35 -0500 Subject: [PATCH] Bash completion for extensions In-Reply-To: References: Message-ID: <109fr67$11d2$1@ciao.gmane.io> Greetings. On 2024-06-14 03:26, S?ndor Semsey wrote: > Bash completion for extensions doesn't work. > Basically `pass.bash-completion` tries to call the extension's > completion function, but it's never sourced, so the function is not > defined, therefore not called. > [...] > > So, I'm submitting a new patch. Looks like your patches got mangled by your mail client or by the mailing list software. (Lines got wrapped, and tabs got converted to spaces.) Here are links to some reformatted versions that apply cleanly: https://github.com/user-attachments/files/22182656/completion.patch https://github.com/user-attachments/files/22182655/man.patch Perhaps Jason might consider testing and applying these? Regards, Tristan -- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Tristan Miller Free Software developer, ferret herder, logologist https://logological.org/ =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- From mekeor at posteo.de Tue Sep 9 09:42:42 2025 From: mekeor at posteo.de (Mekeor Melire) Date: Tue, 09 Sep 2025 09:42:42 +0000 Subject: Commit =?utf-8?Q?=E2=80=9Cemacs=3A?= Avoid double decryption in field and secret =?utf-8?Q?access=E2=80=9D?= broke my setup In-Reply-To: (Tino Calancha's message of "Fri, 29 Aug 2025 13:49:36 +0200") References: <87ikikevzv.fsf@posteo.de> Message-ID: <87h5xcj74j.fsf@posteo.de> 2025-08-29 13:49 tino.calancha at gmail.com: > I think a workaround for your clipboard problem is a simple > Emacs Lisp function that ensures the emacs-daemon process, not > the short-lived emacsclient, handles the clipboard operation. [...] > (defun mypassword-store-copy-to-clipboard-and-wait () > (interactive) > (call-interactively #'password-store-copy)) What makes you think that a wrapping the function call into another function with `call-interactively' will ensure that the emacs-daemon process handles the clipboard operation? I tried it out, it doesn't work, as I expected. Note that we can reduce my problem to a more minimal challenge: Start an Emacs daemon. What can $CODE be so that this puts "foo" into Wayland clipboard? emacsclient -c --eval $CODE --eval '(delete-frame)' For example, CODE='(kill-new "foo")' does not work. I did some more research on my problem. My Emacs is built with pure-GTK (--with-pgtk). If I understand correctly, as GTK-based program, Emacs must assign the ownership of the clipboard selection to a *frame* (rather than a process) when it calls gdk_selection_owner_set_for_display[1] in the Emacs C core function pgtk_own_selection in "src/pgtkselect.c". But now I'm puzzled that my setup even worked at all before the breaking commit. So there must be some $CODE that works! [1] The argument `owner' passed to GTK3-GDK's `selection_owner_set_for_display' function must be a frame. If NULL is passed, the owner is unset. https://docs.gtk.org/gdk3/func.selection_owner_set_for_display.html From esgascoigne at gmail.com Tue Sep 9 10:03:49 2025 From: esgascoigne at gmail.com (Ethan Gascoigne) Date: Tue, 9 Sep 2025 11:03:49 +0100 Subject: Patch: Use VISUAL, then EDITOR, then vi Message-ID: A small patch to make pass use the VISUAL text editor, then EDITOR if not found, then default to vi -------------- next part -------------- A non-text attachment was scrubbed... Name: visual.diff Type: text/x-patch Size: 1463 bytes Desc: not available URL: From mekeor at posteo.de Tue Sep 9 10:55:13 2025 From: mekeor at posteo.de (Mekeor Melire) Date: Tue, 09 Sep 2025 10:55:13 +0000 Subject: Commit =?utf-8?Q?=E2=80=9Cemacs=3A?= Avoid double decryption in field and secret =?utf-8?Q?access=E2=80=9D?= broke my setup In-Reply-To: <87h5xcj74j.fsf@posteo.de> (Mekeor Melire's message of "Tue, 09 Sep 2025 09:42:42 +0000") References: <87ikikevzv.fsf@posteo.de> <87h5xcj74j.fsf@posteo.de> Message-ID: <87cy7zkic3.fsf@posteo.de> 2025-09-09 09:42 mekeor at posteo.de: > Note that we can reduce my problem to a more minimal challenge: > Start an Emacs daemon. What can $CODE be so that this puts "foo" > into Wayland clipboard? > > emacsclient -c --eval $CODE --eval '(delete-frame)' > > For example, CODE='(kill-new "foo")' does not work. I think my setup had been working before because password-store--run-1 evaluates something like (make-process :sentinel (lambda (_ _) (funcall callback output))) where the callback involves `kill-new'. Indeed, when we set CODE="(make-process :name \"foo\" :command '(\"ls\") :sentinel (lambda (_ _) (kill-new \"foo\")))" AND there is another (live) Emacs frame, then "foo" is in the Wayland clipboard. So, apparently my setup had only been working as long as an Emacs frame was alive. (Which is because of the GTK restrictions I explained earlier. I'm not sure if these GTK restriction themselves are consequences of the Wayland primary selection protocol.) I'm not sure about all details but this advice seems to restore the functionality of my previous setup: (define-advice password-store--save-field-in-kill-ring (:around (fun &rest args)) (dolist (frame (frame-list)) (when (frame-live-p frame) (with-selected-frame frame (apply fun args))))) I'm satisfied. From clul+pass at posteo.com.br Thu Sep 11 16:42:07 2025 From: clul+pass at posteo.com.br (Cleverson Casarin Uliana) Date: Thu, 11 Sep 2025 16:42:07 +0000 Subject: Showing a specific line Message-ID: Hello, Currently, you can copy a specific line, e.g. pass -c2 passname, but as far as I understand, you can't show a specific line only. It would be nice to be able to specify e.g. pass show=2 passname, something like this. Maybe it can be done using a Unix tool like grep, is it possible? I find grep's man page too intricate, and other tools like head and cat seem not to allow that. Thanks, Cleverson From Password-Store at grumpmail.org Thu Sep 11 18:09:59 2025 From: Password-Store at grumpmail.org (David Alexander) Date: Thu, 11 Sep 2025 11:09:59 -0700 Subject: Showing a specific line In-Reply-To: References: Message-ID: <9c742b3f-617b-49e5-ba47-df416ade50aa@app.fastmail.com> There are many ways to do that with common tools. For example: pass show ... | head -n 2 | tail -n 1 pass show ... | awk 'NR==2' pass show ... | sed '2p;d' On Thu, Sep 11, 2025, at 9:42 AM, Cleverson Casarin Uliana wrote: > Hello, > Currently, you can copy a specific line, e.g. pass -c2 passname, but as > far as I understand, you can't show a specific line only. It would be > nice to be able to specify e.g. pass show=2 passname, something like this. > > Maybe it can be done using a Unix tool like grep, is it possible? I find > grep's man page too intricate, and other tools like head and cat seem > not to allow that. > > Thanks, > Cleverson From keeperotphones at gmail.com Wed Sep 17 19:50:11 2025 From: keeperotphones at gmail.com (keeperotphones) Date: Wed, 17 Sep 2025 15:50:11 -0400 Subject: Showing a specific line In-Reply-To: <9c742b3f-617b-49e5-ba47-df416ade50aa@app.fastmail.com> References: <9c742b3f-617b-49e5-ba47-df416ade50aa@app.fastmail.com> Message-ID: On 25/09/11 11:09AM, David Alexander wrote: > On Thu, Sep 11, 2025, at 9:42 AM, Cleverson Casarin Uliana wrote: > > Hello, > > Currently, you can copy a specific line, e.g. pass -c2 passname, but as > > far as I understand, you can't show a specific line only. It would be > > nice to be able to specify e.g. pass show=2 passname, something like this. > > > > Maybe it can be done using a Unix tool like grep, is it possible? I find > > grep's man page too intricate, and other tools like head and cat seem > > not to allow that. > > There are many ways to do that with common tools. For example: > > pass show ... | head -n 2 | tail -n 1 > pass show ... | awk 'NR==2' > pass show ... | sed '2p;d' > If we have this behavior for qrencoding and clipboard it would make sense to have this as a feature for display, really. I have remained silent in noticing this over the years, but there is a good point here, to have non-clipboard ability available here. From clul+pass at posteo.com.br Wed Sep 17 19:59:53 2025 From: clul+pass at posteo.com.br (Cleverson Casarin Uliana) Date: Wed, 17 Sep 2025 19:59:53 +0000 Subject: Showing a specific line In-Reply-To: References: <9c742b3f-617b-49e5-ba47-df416ade50aa@app.fastmail.com> Message-ID: <9eed5708-fb60-4fac-8d31-672ce544ca06@posteo.com.br> Hi, thank you both for answering. I found the sed solution in particular quite elegant. In any case, I agree that implementing line number directly into the show command may be more consistent. Greetings, Cleverson From Password-Store at grumpmail.org Thu Sep 18 18:46:48 2025 From: Password-Store at grumpmail.org (David Alexander) Date: Thu, 18 Sep 2025 11:46:48 -0700 Subject: Showing a specific line In-Reply-To: References: <9c742b3f-617b-49e5-ba47-df416ade50aa@app.fastmail.com> Message-ID: Not necessarily. When sending to the clipboard you HAVE to filter what gets sent. You don't want to populate the clipboard with the entire contents of an entry because that would make the clipboard useless. Same for QR codes. But with the show (to stdout) command it's very easy to continue filtering if desired. Not that I mind if this gets implemented. On Wed, Sep 17, 2025, at 12:50 PM, keeperotphones wrote: > If we have this behavior for qrencoding and clipboard it would make sense to > have this as a feature for display, really. From keeperotphones at gmail.com Sat Sep 20 04:29:41 2025 From: keeperotphones at gmail.com (keeperotphones) Date: Sat, 20 Sep 2025 00:29:41 -0400 Subject: Showing a specific line In-Reply-To: References: <9c742b3f-617b-49e5-ba47-df416ade50aa@app.fastmail.com> Message-ID: On 25/09/18 11:46AM, David Alexander wrote: > Not necessarily. When sending to the clipboard you HAVE to filter what gets sent. You don't want to populate the clipboard with the entire contents of an entry because that would make the clipboard useless. Same for QR codes. But with the show (to stdout) command it's very easy to continue filtering if desired. Not exactly sure what you are responding to, but the -c indicates clipoard, the number indicates the line, and so in this case the tool does the filtering. Having a "l" option, maybe, to only show a single line, or accept a range, perhaps, seems like it would be something that I would use for the `pass show` command. However, populating the "entire contents" of an entry is something that I do often, as this is a "free form" system, so there are some entries where the entire content may consist of up to a single line. (It may or may not have the EOL character). Then the entire content in the clipboard is handy. And it is useful for me. Not sure why it would be considered, inherently. I also accept that simplicity and the Unix Philosophy often wins out. From szeder.dev at gmail.com Sat Sep 20 11:22:07 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:07 +0200 Subject: [PATCH 00/10] Improvements for multiline password files Message-ID: <20250920112217.3904292-1-szeder.dev@gmail.com> This patch series extends 'pass show' to be able to print only a specific line from a multiline password file to its standard output and makes it possible to specify that line by its field name prefix instead of its line number. The main motivation was that I wanted to use 'msmtp' to send emails via my GMail account from a system with AppArmor enabled by default, but: - Google insist on using a so called app password in such cases. - I wanted to store this app password in the same multiline password file where all other information related to my Google Account are stored like this: abie4lohTi8geif4 login: john.doe at gmail.com URL: *.google.com/* app-password: bae2vohnohth3Bahphei - The default systemwide AppArmor profile for 'msmtp' does allow it to invoke 'pass', but prevents it from invoking the shell commands necessary to select the line containing the app password and to strip off the field name prefix. With this patch series 'msmtp' can be configured to get the password by running this command without bumping into AppArmor's restictions: pass show --stdout=app-password --strip-field google.com Patches 1-6 are preparatory refactorings and documentation updates. Patches 7-9 add the new capabilities to 'pass show'. Patch 10 fixes the SIGPIPE issue with large password files that has been reported a couple of times in the past, and does it in a way that fits well with the other changes in this patch series. Note that this series modifies the fish and zsh completions, but I'm not familiar with those shells and was merely following the patterns set by existing completions, and have not actually tested them. This patch series is also available at: https://codeberg.org/szeder/password-store/ multiline-improvements https://github.com/szeder/password-store/ multiline-improvements SZEDER G?bor (10): completion: add -q|--qrcode options to 'pass show' Document 'pass show's -q|--qrcode options in 'pass help' Document the line-number argument of the various 'pass show' options show: clarify error message show: refactor mutually exclusive option check show: add extract_one_line() helper function show: add '--stdout[=line-number]' option show: add '--strip-field' option show: allow selecting the output line by field name show: avoid errors due to SIGPIPE man/pass.1 | 23 ++++++++- src/completion/pass.bash-completion | 2 +- src/completion/pass.fish-completion | 11 +++++ src/completion/pass.zsh-completion | 7 ++- src/password-store.sh | 46 ++++++++++++++---- tests/t0020-show-tests.sh | 75 +++++++++++++++++++++++++++++ 6 files changed, 151 insertions(+), 13 deletions(-) -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:08 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:08 +0200 Subject: [PATCH 01/10] completion: add -q|--qrcode options to 'pass show' In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-2-szeder.dev@gmail.com> Let's bring it up to date before adding new options in later commits. --- src/completion/pass.bash-completion | 2 +- src/completion/pass.fish-completion | 4 ++++ src/completion/pass.zsh-completion | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/completion/pass.bash-completion b/src/completion/pass.bash-completion index 2d23cbf..f4c1584 100644 --- a/src/completion/pass.bash-completion +++ b/src/completion/pass.bash-completion @@ -101,7 +101,7 @@ _pass() _pass_complete_entries ;; show|-*) - COMPREPLY+=($(compgen -W "-c --clip" -- ${cur})) + COMPREPLY+=($(compgen -W "-c --clip -q --qrcode" -- ${cur})) _pass_complete_entries 1 ;; insert) diff --git a/src/completion/pass.fish-completion b/src/completion/pass.fish-completion index 0f57dd2..6117ad8 100644 --- a/src/completion/pass.fish-completion +++ b/src/completion/pass.fish-completion @@ -97,12 +97,16 @@ complete -c $PROG -f -n '__fish_pass_uses_command edit' -a "(__fish_pass_print_e complete -c $PROG -f -n '__fish_pass_needs_command' -a show -d 'Command: show existing password' complete -c $PROG -f -n '__fish_pass_uses_command show' -s c -l clip -d 'Put password in clipboard' +complete -c $PROG -f -n '__fish_pass_uses_command show' -s q -l qrcode -d 'Display QR code' complete -c $PROG -f -n '__fish_pass_uses_command show' -a "(__fish_pass_print_entries)" # When no command is given, `show` is defaulted. complete -c $PROG -f -n '__fish_pass_needs_command' -s c -l clip -d 'Put password in clipboard' +complete -c $PROG -f -n '__fish_pass_needs_command' -s q -l qrcode -d 'Display QR code' complete -c $PROG -f -n '__fish_pass_needs_command' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command -c' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command --clip' -a "(__fish_pass_print_entries)" +complete -c $PROG -f -n '__fish_pass_uses_command -q' -a "(__fish_pass_print_entries)" +complete -c $PROG -f -n '__fish_pass_uses_command --qrcode' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_needs_command' -a git -d 'Command: execute a git command' complete -c $PROG -f -n '__fish_pass_uses_command git' -a '(__fish_pass_git_complete)' diff --git a/src/completion/pass.zsh-completion b/src/completion/pass.zsh-completion index d911e12..5041365 100644 --- a/src/completion/pass.zsh-completion +++ b/src/completion/pass.zsh-completion @@ -117,7 +117,9 @@ _pass () { _pass_cmd_show () { _arguments : \ "-c[put it on the clipboard]" \ - "--clip[put it on the clipboard]" + "--clip[put it on the clipboard]" \ + "-q[display QR code]" \ + "--qrcode[display QR code]" _pass_complete_entries } _pass_complete_entries_helper () { -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:09 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:09 +0200 Subject: [PATCH 02/10] Document 'pass show's -q|--qrcode options in 'pass help' In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-3-szeder.dev@gmail.com> Let's bring it up to date before adding new options in later commits. --- src/password-store.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/password-store.sh b/src/password-store.sh index 22e818f..eab56f3 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -284,8 +284,8 @@ cmd_usage() { List passwords. $PROGRAM find pass-names... List passwords that match pass-names. - $PROGRAM [show] [--clip[=line-number],-c[line-number]] pass-name - Show existing password and optionally put it on the clipboard. + $PROGRAM [show] [--clip[=line-number],-c[line-number],--qrcode[=line-number],-q[line-number]] pass-name + Show existing password and optionally put it on the clipboard or display as QR code. If put on the clipboard, it will be cleared in $CLIP_TIME seconds. $PROGRAM grep [GREPOPTIONS] search-string Search for password files containing search-string when decrypted. -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:10 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:10 +0200 Subject: [PATCH 03/10] Document the line-number argument of the various 'pass show' options In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-4-szeder.dev@gmail.com> --- man/pass.1 | 3 ++- src/password-store.sh | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/man/pass.1 b/man/pass.1 index a555dcb..2286359 100644 --- a/man/pass.1 +++ b/man/pass.1 @@ -104,7 +104,8 @@ or and then restore the clipboard after 45 (or \fIPASSWORD_STORE_CLIP_TIME\fP) seconds. If \fI--qrcode\fP or \fI-q\fP is specified, do not print the password but instead display a QR code using .BR qrencode (1) -either to the terminal or graphically if supported. +either to the terminal or graphically if supported. If \fIline-number\fP is specified, +then only that line will be copied or displayed from a multiline password. .TP \fBinsert\fP [ \fI--echo\fP, \fI-e\fP | \fI--multiline\fP, \fI-m\fP ] [ \fI--force\fP, \fI-f\fP ] \fIpass-name\fP Insert a new password into the password store called \fIpass-name\fP. This will diff --git a/src/password-store.sh b/src/password-store.sh index eab56f3..b1a2712 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -287,6 +287,7 @@ cmd_usage() { $PROGRAM [show] [--clip[=line-number],-c[line-number],--qrcode[=line-number],-q[line-number]] pass-name Show existing password and optionally put it on the clipboard or display as QR code. If put on the clipboard, it will be cleared in $CLIP_TIME seconds. + If a line-number is given, then only that line will be copied or displayed from a multiline password. $PROGRAM grep [GREPOPTIONS] search-string Search for password files containing search-string when decrypted. $PROGRAM insert [--echo,-e | --multiline,-m] [--force,-f] pass-name -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:11 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:11 +0200 Subject: [PATCH 04/10] show: clarify error message In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-5-szeder.dev@gmail.com> In case of a non-existing or empty line, 'pass show --qrcode=42 passname' dies with the error message "There is no password to put on the clipboard at line 42", even though the '--qrcode' option doesn't involve the clipboard at all. Update this error message to be more generic and to merely say: "There is no password at line 42". --- src/password-store.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/password-store.sh b/src/password-store.sh index b1a2712..ce0e339 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -390,7 +390,7 @@ cmd_show() { else [[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location '$selected_line' is not a number." pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | tail -n +${selected_line} | head -n 1)" || exit $? - [[ -n $pass ]] || die "There is no password to put on the clipboard at line ${selected_line}." + [[ -n $pass ]] || die "There is no password at line ${selected_line}." if [[ $clip -eq 1 ]]; then clip "$pass" "$path" elif [[ $qrcode -eq 1 ]]; then -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:12 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:12 +0200 Subject: [PATCH 05/10] show: refactor mutually exclusive option check In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-6-szeder.dev@gmail.com> 'pass show's '--clip' and '--qrcode' options are mutually exclusive, and 'pass' aborts showing usage when the user specifies both. The condition catching this checks whether both variables corresponding to each of these options are set (i.e. '$qrcode -eq 1 && $clip -eq 1'). This is fine for now when there are only two mutually exclusive options, but it won't scale when we add more such options. Refactor this check so it scales better to arbitrary number of mutually exclusive options: the variables corresponding to those options have a value of either '1' or '0', so let's compute their arithmetic sum, and error out when their sum is greater than 1. --- src/password-store.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/password-store.sh b/src/password-store.sh index ce0e339..7b1f929 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -377,14 +377,15 @@ cmd_show() { --) shift; break ;; esac done - [[ $err -ne 0 || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [pass-name]" + local output_opts=$(( $qrcode + $clip )) + [[ $err -ne 0 || $output_opts -gt 1 ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [pass-name]" local pass local path="$1" local passfile="$PREFIX/$path.gpg" check_sneaky_paths "$path" if [[ -f $passfile ]]; then - if [[ $clip -eq 0 && $qrcode -eq 0 ]]; then + if [[ $output_opts -eq 0 ]]; then pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $? echo "$pass" | $BASE64 -d else -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:13 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:13 +0200 Subject: [PATCH 06/10] show: add extract_one_line() helper function In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-7-szeder.dev@gmail.com> A shell pipeline involving 'tail' and 'head' are responsible for extracting a specific line from a multiline password file for '--clip=2' and '--qrcode=3'. With some fixes and features in upcoming commits these pipeline stages will get more complicated, so let's raise the level of abstraction and extract them into a dedicated helper function. --- src/password-store.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/password-store.sh b/src/password-store.sh index 7b1f929..bda15d5 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -146,6 +146,12 @@ check_sneaky_paths() { done } +# Extract the given line from standard input. +extract_one_line() { + local selected_line="$1" + tail -n +"$selected_line" | head -n 1 +} + # # END helper functions # @@ -390,7 +396,7 @@ cmd_show() { echo "$pass" | $BASE64 -d else [[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location '$selected_line' is not a number." - pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | tail -n +${selected_line} | head -n 1)" || exit $? + pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | extract_one_line "$selected_line")" || exit $? [[ -n $pass ]] || die "There is no password at line ${selected_line}." if [[ $clip -eq 1 ]]; then clip "$pass" "$path" -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:14 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:14 +0200 Subject: [PATCH 07/10] show: add '--stdout[=line-number]' option In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-8-szeder.dev@gmail.com> When dealing with multiline password files, the options '--clip=1' or '--qrcode=2' allow to take only the specified line and copy its content to clipboard or display as QR code. There is, however, no equivalent option to print only one specified line of a multiline password file to standard output. While it's fairly straightforward to extract a single line from the output of 'pass' using a pipeline and a couple of shell commands, just like 'pass' itself does internally for '--clip=1', it's not always applicable. E.g. many systems use AppArmor to restrict programs' capabilities, and even if the user can configure a program to run 'pass' and those pipeline commands to supply a password, the program's default AppArmor profile might only allow invoking 'pass', but prevent it from invoking those pipeline commands necessary to extract a single line from the multiline output. Provide better support for those systems by adding the '-s|--stdout' option with an optional line number parameter to print just the specified line from the password file to standard output. --- man/pass.1 | 4 ++-- src/completion/pass.bash-completion | 2 +- src/completion/pass.fish-completion | 4 ++++ src/completion/pass.zsh-completion | 4 +++- src/password-store.sh | 15 +++++++++------ tests/t0020-show-tests.sh | 24 ++++++++++++++++++++++++ 6 files changed, 43 insertions(+), 10 deletions(-) diff --git a/man/pass.1 b/man/pass.1 index 2286359..fc34e08 100644 --- a/man/pass.1 +++ b/man/pass.1 @@ -94,7 +94,7 @@ List names of passwords inside the tree that match \fIpass-names\fP by using the .BR tree (1) program. This command is alternatively named \fBsearch\fP. .TP -\fBshow\fP [ \fI--clip\fP[=\fIline-number\fP], \fI-c\fP[\fIline-number\fP] ] [ \fI--qrcode\fP[=\fIline-number\fP], \fI-q\fP[\fIline-number\fP] ] \fIpass-name\fP +\fBshow\fP [ \fI--clip\fP[=\fIline-number\fP], \fI-c\fP[\fIline-number\fP] ] [ \fI--qrcode\fP[=\fIline-number\fP], \fI-q\fP[\fIline-number\fP] ] [ \fI--stdout\fP[=\fIline-number\fP], \fI-s\fP[\fIline-number\fP] ] \fIpass-name\fP Decrypt and print a password named \fIpass-name\fP. If \fI--clip\fP or \fI-c\fP is specified, do not print the password but instead copy the first (or otherwise specified) line to the clipboard using @@ -105,7 +105,7 @@ and then restore the clipboard after 45 (or \fIPASSWORD_STORE_CLIP_TIME\fP) seco or \fI-q\fP is specified, do not print the password but instead display a QR code using .BR qrencode (1) either to the terminal or graphically if supported. If \fIline-number\fP is specified, -then only that line will be copied or displayed from a multiline password. +then only that line will be copied, displayed or printed from a multiline password. .TP \fBinsert\fP [ \fI--echo\fP, \fI-e\fP | \fI--multiline\fP, \fI-m\fP ] [ \fI--force\fP, \fI-f\fP ] \fIpass-name\fP Insert a new password into the password store called \fIpass-name\fP. This will diff --git a/src/completion/pass.bash-completion b/src/completion/pass.bash-completion index f4c1584..e172b4c 100644 --- a/src/completion/pass.bash-completion +++ b/src/completion/pass.bash-completion @@ -101,7 +101,7 @@ _pass() _pass_complete_entries ;; show|-*) - COMPREPLY+=($(compgen -W "-c --clip -q --qrcode" -- ${cur})) + COMPREPLY+=($(compgen -W "-c --clip -q --qrcode -s --stdout" -- ${cur})) _pass_complete_entries 1 ;; insert) diff --git a/src/completion/pass.fish-completion b/src/completion/pass.fish-completion index 6117ad8..63d7ea5 100644 --- a/src/completion/pass.fish-completion +++ b/src/completion/pass.fish-completion @@ -98,15 +98,19 @@ complete -c $PROG -f -n '__fish_pass_uses_command edit' -a "(__fish_pass_print_e complete -c $PROG -f -n '__fish_pass_needs_command' -a show -d 'Command: show existing password' complete -c $PROG -f -n '__fish_pass_uses_command show' -s c -l clip -d 'Put password in clipboard' complete -c $PROG -f -n '__fish_pass_uses_command show' -s q -l qrcode -d 'Display QR code' +complete -c $PROG -f -n '__fish_pass_uses_command show' -s s -l stdout -d 'Print to standard output' complete -c $PROG -f -n '__fish_pass_uses_command show' -a "(__fish_pass_print_entries)" # When no command is given, `show` is defaulted. complete -c $PROG -f -n '__fish_pass_needs_command' -s c -l clip -d 'Put password in clipboard' complete -c $PROG -f -n '__fish_pass_needs_command' -s q -l qrcode -d 'Display QR code' +complete -c $PROG -f -n '__fish_pass_needs_command' -s s -l stdout -d 'Print to standard output' complete -c $PROG -f -n '__fish_pass_needs_command' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command -c' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command --clip' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command -q' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command --qrcode' -a "(__fish_pass_print_entries)" +complete -c $PROG -f -n '__fish_pass_uses_command -s' -a "(__fish_pass_print_entries)" +complete -c $PROG -f -n '__fish_pass_uses_command --stdout' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_needs_command' -a git -d 'Command: execute a git command' complete -c $PROG -f -n '__fish_pass_uses_command git' -a '(__fish_pass_git_complete)' diff --git a/src/completion/pass.zsh-completion b/src/completion/pass.zsh-completion index 5041365..cf09cd5 100644 --- a/src/completion/pass.zsh-completion +++ b/src/completion/pass.zsh-completion @@ -119,7 +119,9 @@ _pass_cmd_show () { "-c[put it on the clipboard]" \ "--clip[put it on the clipboard]" \ "-q[display QR code]" \ - "--qrcode[display QR code]" + "--qrcode[display QR code]" \ + "-s[print to standard output]" \ + "--stdout[print to standard output]" _pass_complete_entries } _pass_complete_entries_helper () { diff --git a/src/password-store.sh b/src/password-store.sh index bda15d5..591a0c0 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -290,10 +290,10 @@ cmd_usage() { List passwords. $PROGRAM find pass-names... List passwords that match pass-names. - $PROGRAM [show] [--clip[=line-number],-c[line-number],--qrcode[=line-number],-q[line-number]] pass-name + $PROGRAM [show] [--clip[=line-number],-c[line-number],--qrcode[=line-number],-q[line-number],--stdout[=line-number],-s[line-number]] pass-name Show existing password and optionally put it on the clipboard or display as QR code. If put on the clipboard, it will be cleared in $CLIP_TIME seconds. - If a line-number is given, then only that line will be copied or displayed from a multiline password. + If a line-number is given, then only that line will be copied, displayed or printed from a multiline password. $PROGRAM grep [GREPOPTIONS] search-string Search for password files containing search-string when decrypted. $PROGRAM insert [--echo,-e | --multiline,-m] [--force,-f] pass-name @@ -373,18 +373,19 @@ cmd_init() { } cmd_show() { - local opts selected_line clip=0 qrcode=0 - opts="$($GETOPT -o q::c:: -l qrcode::,clip:: -n "$PROGRAM" -- "$@")" + local opts selected_line clip=0 qrcode=0 stdout=0 + opts="$($GETOPT -o q::c::s:: -l qrcode::,clip::,stdout:: -n "$PROGRAM" -- "$@")" local err=$? eval set -- "$opts" while true; do case $1 in -q|--qrcode) qrcode=1; selected_line="${2:-1}"; shift 2 ;; -c|--clip) clip=1; selected_line="${2:-1}"; shift 2 ;; + -s|--stdout) stdout=1; selected_line="${2:-1}"; shift 2 ;; --) shift; break ;; esac done - local output_opts=$(( $qrcode + $clip )) - [[ $err -ne 0 || $output_opts -gt 1 ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [pass-name]" + local output_opts=$(( $qrcode + $clip + $stdout )) + [[ $err -ne 0 || $output_opts -gt 1 ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [--stdout[=line-number],-s[line-number]] [pass-name]" local pass local path="$1" @@ -402,6 +403,8 @@ cmd_show() { clip "$pass" "$path" elif [[ $qrcode -eq 1 ]]; then qrcode "$pass" "$path" + elif [[ $stdout -eq 1 ]]; then + printf '%s\n' "$pass" fi fi elif [[ -d $PREFIX/$path ]]; then diff --git a/tests/t0020-show-tests.sh b/tests/t0020-show-tests.sh index 81b87b4..c961d5f 100755 --- a/tests/t0020-show-tests.sh +++ b/tests/t0020-show-tests.sh @@ -24,4 +24,28 @@ test_expect_success 'Test "show" of nonexistant password' ' test_must_fail "$PASS" show cred2 ' +test_expect_success 'Test "show" command with multiline password' ' + cat >content <<-\EOF && + p4$$w0rd + second: twotwo + third: threethree + fourth: fourfour + EOF + "$PASS" insert -m multiline actual && + test_cmp content actual +' + +test_expect_success 'Test "show --stdout"' ' + echo "second: twotwo" >expect && + "$PASS" show --stdout=2 multiline >actual && + test_cmp expect actual +' + +test_expect_success 'Test "show --stdout" with out-of-range line-number' ' + test_must_fail "$PASS" show --stdout=42 multiline >actual 2>stderr && + test ! -s actual && + grep "There is no password at line 42" stderr +' + test_done -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:15 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:15 +0200 Subject: [PATCH 08/10] show: add '--strip-field' option In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-9-szeder.dev@gmail.com> Some services use a so called app password that, quoting Google's support site about app passwords, "gives a less secure app or device permission to access" the account, e.g. to access GMail with 'msmtp' and 'mbsync'. To handle such an app password with 'pass', it's convenient to store the app password in the same multiline password file as the other credentials and metadata related to that account like this: abie4lohTi8geif4 login: john.doe at gmail.com URL: *.google.com/* app-password: bae2vohnohth3Bahphei When providing such an app password to a program that field name prefix must be removed from the output of 'pass', of course. While it's fairly straightforward to do so using a pipeline and a couple of shell commands, it's not always applicable. Just like in the previous commit, the problem is with restrictions imposed by AppArmor: even if the user can configure a program to run 'pass' and the pipeline commands to supply the app password, the program's default AppArmor profile might only allow invoking 'pass', but prevent it from invoking those pipeline commands necessary to strip that field name prefix. Provide better support for those systems by adding the '--strip-field' option to remove that field name prefix, i.e. everything up to and including the first ': '. $ pass show --stdout=4 --strip-field google.com bae2vohnohth3Bahphei --- man/pass.1 | 2 +- src/completion/pass.bash-completion | 2 +- src/completion/pass.fish-completion | 3 +++ src/completion/pass.zsh-completion | 3 ++- src/password-store.sh | 11 +++++++---- tests/t0020-show-tests.sh | 18 ++++++++++++++++++ 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/man/pass.1 b/man/pass.1 index fc34e08..6f78b44 100644 --- a/man/pass.1 +++ b/man/pass.1 @@ -94,7 +94,7 @@ List names of passwords inside the tree that match \fIpass-names\fP by using the .BR tree (1) program. This command is alternatively named \fBsearch\fP. .TP -\fBshow\fP [ \fI--clip\fP[=\fIline-number\fP], \fI-c\fP[\fIline-number\fP] ] [ \fI--qrcode\fP[=\fIline-number\fP], \fI-q\fP[\fIline-number\fP] ] [ \fI--stdout\fP[=\fIline-number\fP], \fI-s\fP[\fIline-number\fP] ] \fIpass-name\fP +\fBshow\fP [ \fI--clip\fP[=\fIline-number\fP], \fI-c\fP[\fIline-number\fP] ] [ \fI--qrcode\fP[=\fIline-number\fP], \fI-q\fP[\fIline-number\fP] ] [ \fI--stdout\fP[=\fIline-number\fP], \fI-s\fP[\fIline-number\fP] ] [ \fI--strip-field\fP ] \fIpass-name\fP Decrypt and print a password named \fIpass-name\fP. If \fI--clip\fP or \fI-c\fP is specified, do not print the password but instead copy the first (or otherwise specified) line to the clipboard using diff --git a/src/completion/pass.bash-completion b/src/completion/pass.bash-completion index e172b4c..a80b1fd 100644 --- a/src/completion/pass.bash-completion +++ b/src/completion/pass.bash-completion @@ -101,7 +101,7 @@ _pass() _pass_complete_entries ;; show|-*) - COMPREPLY+=($(compgen -W "-c --clip -q --qrcode -s --stdout" -- ${cur})) + COMPREPLY+=($(compgen -W "-c --clip -q --qrcode -s --stdout --strip-field" -- ${cur})) _pass_complete_entries 1 ;; insert) diff --git a/src/completion/pass.fish-completion b/src/completion/pass.fish-completion index 63d7ea5..4c49cf1 100644 --- a/src/completion/pass.fish-completion +++ b/src/completion/pass.fish-completion @@ -99,11 +99,13 @@ complete -c $PROG -f -n '__fish_pass_needs_command' -a show -d 'Command: show ex complete -c $PROG -f -n '__fish_pass_uses_command show' -s c -l clip -d 'Put password in clipboard' complete -c $PROG -f -n '__fish_pass_uses_command show' -s q -l qrcode -d 'Display QR code' complete -c $PROG -f -n '__fish_pass_uses_command show' -s s -l stdout -d 'Print to standard output' +complete -c $PROG -f -n '__fish_pass_uses_command show' -l strip-field -d 'Strip field prefix' complete -c $PROG -f -n '__fish_pass_uses_command show' -a "(__fish_pass_print_entries)" # When no command is given, `show` is defaulted. complete -c $PROG -f -n '__fish_pass_needs_command' -s c -l clip -d 'Put password in clipboard' complete -c $PROG -f -n '__fish_pass_needs_command' -s q -l qrcode -d 'Display QR code' complete -c $PROG -f -n '__fish_pass_needs_command' -s s -l stdout -d 'Print to standard output' +complete -c $PROG -f -n '__fish_pass_needs_command' -l strip-field -d 'Strip field prefix' complete -c $PROG -f -n '__fish_pass_needs_command' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command -c' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command --clip' -a "(__fish_pass_print_entries)" @@ -111,6 +113,7 @@ complete -c $PROG -f -n '__fish_pass_uses_command -q' -a "(__fish_pass_print_ent complete -c $PROG -f -n '__fish_pass_uses_command --qrcode' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command -s' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_uses_command --stdout' -a "(__fish_pass_print_entries)" +complete -c $PROG -f -n '__fish_pass_uses_command --strip-field' -a "(__fish_pass_print_entries)" complete -c $PROG -f -n '__fish_pass_needs_command' -a git -d 'Command: execute a git command' complete -c $PROG -f -n '__fish_pass_uses_command git' -a '(__fish_pass_git_complete)' diff --git a/src/completion/pass.zsh-completion b/src/completion/pass.zsh-completion index cf09cd5..8494489 100644 --- a/src/completion/pass.zsh-completion +++ b/src/completion/pass.zsh-completion @@ -121,7 +121,8 @@ _pass_cmd_show () { "-q[display QR code]" \ "--qrcode[display QR code]" \ "-s[print to standard output]" \ - "--stdout[print to standard output]" + "--stdout[print to standard output]" \ + "--strip-field[strip field prefix]" _pass_complete_entries } _pass_complete_entries_helper () { diff --git a/src/password-store.sh b/src/password-store.sh index 591a0c0..c67a890 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -290,7 +290,7 @@ cmd_usage() { List passwords. $PROGRAM find pass-names... List passwords that match pass-names. - $PROGRAM [show] [--clip[=line-number],-c[line-number],--qrcode[=line-number],-q[line-number],--stdout[=line-number],-s[line-number]] pass-name + $PROGRAM [show] [--clip[=line-number],-c[line-number],--qrcode[=line-number],-q[line-number],--stdout[=line-number],-s[line-number] [--strip-field]] pass-name Show existing password and optionally put it on the clipboard or display as QR code. If put on the clipboard, it will be cleared in $CLIP_TIME seconds. If a line-number is given, then only that line will be copied, displayed or printed from a multiline password. @@ -373,19 +373,21 @@ cmd_init() { } cmd_show() { - local opts selected_line clip=0 qrcode=0 stdout=0 - opts="$($GETOPT -o q::c::s:: -l qrcode::,clip::,stdout:: -n "$PROGRAM" -- "$@")" + local opts selected_line clip=0 qrcode=0 stdout=0 strip_field=0 + opts="$($GETOPT -o q::c::s:: -l qrcode::,clip::,stdout::,strip-field -n "$PROGRAM" -- "$@")" local err=$? eval set -- "$opts" while true; do case $1 in -q|--qrcode) qrcode=1; selected_line="${2:-1}"; shift 2 ;; -c|--clip) clip=1; selected_line="${2:-1}"; shift 2 ;; -s|--stdout) stdout=1; selected_line="${2:-1}"; shift 2 ;; + --strip-field) strip_field=1; shift ;; --) shift; break ;; esac done local output_opts=$(( $qrcode + $clip + $stdout )) - [[ $err -ne 0 || $output_opts -gt 1 ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [--stdout[=line-number],-s[line-number]] [pass-name]" + [[ $err -ne 0 || $output_opts -gt 1 ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [--stdout[=line-number],-s[line-number]] [--strip-field] [pass-name]" + [[ $output_opts -eq 0 && $strip_field -eq 1 ]] && die "'--strip-field' only works on a specific line" local pass local path="$1" @@ -398,6 +400,7 @@ cmd_show() { else [[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location '$selected_line' is not a number." pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | extract_one_line "$selected_line")" || exit $? + [[ $strip_field -eq 1 ]] && pass="${pass#*: }" [[ -n $pass ]] || die "There is no password at line ${selected_line}." if [[ $clip -eq 1 ]]; then clip "$pass" "$path" diff --git a/tests/t0020-show-tests.sh b/tests/t0020-show-tests.sh index c961d5f..9a7a0b4 100755 --- a/tests/t0020-show-tests.sh +++ b/tests/t0020-show-tests.sh @@ -42,6 +42,24 @@ test_expect_success 'Test "show --stdout"' ' test_cmp expect actual ' +test_expect_success 'Test "show --stdout --strip-field"' ' + echo "threethree" >expect && + "$PASS" show --stdout=3 --strip-field multiline >actual && + test_cmp expect actual +' + +test_expect_success 'Test "show --stdout --strip-field" with extra colon' ' + echo "pass: word" >expect && + echo "field: pass: word" | "$PASS" insert -m extra-colon && + "$PASS" show --stdout=1 --strip-field extra-colon >actual && + test_cmp expect actual +' + +test_expect_success 'Test "show --strip-field" with multiple lines' ' + test_must_fail "$PASS" show --strip-field multiline 2>stderr && + grep ".--strip-field. only works on a specific line" stderr +' + test_expect_success 'Test "show --stdout" with out-of-range line-number' ' test_must_fail "$PASS" show --stdout=42 multiline >actual 2>stderr && test ! -s actual && -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:16 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:16 +0200 Subject: [PATCH 09/10] show: allow selecting the output line by field name In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-10-szeder.dev@gmail.com> We can select a specific line from a multiline password file by specifying its line number to the --stdout, --clip or --qrcode options. However, names are much more descriptive and easier to remember than numbers, and they can be used consistently accross multiple multiline password files. So, let's extend the optional argument of these options to allow selecting a line by its field name. I.e. if the line selector argument is a number, then select the Nth line from the password file (so the existing behavior doesn't change), or else treat it as a field name, and select the first line that starts with that name (followed by ': '). --- man/pass.1 | 22 ++++++++++++++++++++-- src/password-store.sh | 20 ++++++++++++++++---- tests/t0020-show-tests.sh | 24 ++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/man/pass.1 b/man/pass.1 index 6f78b44..4a9d474 100644 --- a/man/pass.1 +++ b/man/pass.1 @@ -94,7 +94,7 @@ List names of passwords inside the tree that match \fIpass-names\fP by using the .BR tree (1) program. This command is alternatively named \fBsearch\fP. .TP -\fBshow\fP [ \fI--clip\fP[=\fIline-number\fP], \fI-c\fP[\fIline-number\fP] ] [ \fI--qrcode\fP[=\fIline-number\fP], \fI-q\fP[\fIline-number\fP] ] [ \fI--stdout\fP[=\fIline-number\fP], \fI-s\fP[\fIline-number\fP] ] [ \fI--strip-field\fP ] \fIpass-name\fP +\fBshow\fP [ \fI--clip\fP[=\fIline-selector\fP], \fI-c\fP[\fIline-selector\fP] ] [ \fI--qrcode\fP[=\fIline-selector\fP], \fI-q\fP[\fIline-selector\fP] ] [ \fI--stdout\fP[=\fIline-selector\fP], \fI-s\fP[\fIline-selector\fP] ] [ \fI--strip-field\fP ] \fIpass-name\fP Decrypt and print a password named \fIpass-name\fP. If \fI--clip\fP or \fI-c\fP is specified, do not print the password but instead copy the first (or otherwise specified) line to the clipboard using @@ -104,8 +104,11 @@ or and then restore the clipboard after 45 (or \fIPASSWORD_STORE_CLIP_TIME\fP) seconds. If \fI--qrcode\fP or \fI-q\fP is specified, do not print the password but instead display a QR code using .BR qrencode (1) -either to the terminal or graphically if supported. If \fIline-number\fP is specified, +either to the terminal or graphically if supported. If \fIline-selector\fP is specified, then only that line will be copied, displayed or printed from a multiline password. +If \fIline-selector\fP is a number, then it selects the Nth line; otherwise it +is treated as a field name, and the first line with the matching field is +selected. .TP \fBinsert\fP [ \fI--echo\fP, \fI-e\fP | \fI--multiline\fP, \fI-m\fP ] [ \fI--force\fP, \fI-f\fP ] \fIpass-name\fP Insert a new password into the password store called \fIpass-name\fP. This will @@ -273,6 +276,21 @@ passworrrrrrrrd. .br ^D .TP +Print the content of a specific line from a multiline password file without the field name prefix +.B zx2c4 at laptop ~ $ pass show example.com +.br +abie4lohTi8geif4 +.br +login: john.doe at example.com +.br +URL: *.example.com/* +.br +app-password: bae2vohnohth3Bahphei +.br +.B zx2c4 at laptop ~ $ pass show --stdout=app-password --strip-field example.com +.br +bae2vohnohth3Bahphei +.TP Generate new password .B zx2c4 at laptop ~ $ pass generate Email/jasondonenfeld.com 15 .br diff --git a/src/password-store.sh b/src/password-store.sh index c67a890..3f673ef 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -146,10 +146,22 @@ check_sneaky_paths() { done } +# Like 'grep', but it returns 0 even if no matches were found, to avoid +# triggering '-o pipefail'. +grep_gently() { + grep "$@" + true +} + # Extract the given line from standard input. extract_one_line() { local selected_line="$1" - tail -n +"$selected_line" | head -n 1 + if [[ "$selected_line" =~ ^[0-9]+$ ]]; then + tail -n +$selected_line + else + grep_gently "^$selected_line: " + fi | + head -n 1 } # @@ -290,10 +302,11 @@ cmd_usage() { List passwords. $PROGRAM find pass-names... List passwords that match pass-names. - $PROGRAM [show] [--clip[=line-number],-c[line-number],--qrcode[=line-number],-q[line-number],--stdout[=line-number],-s[line-number] [--strip-field]] pass-name + $PROGRAM [show] [--clip[=line-selector],-c[line-selector],--qrcode[=line-selector],-q[line-selector],--stdout[=line-selector],-s[line-selector] [--strip-field]] pass-name Show existing password and optionally put it on the clipboard or display as QR code. If put on the clipboard, it will be cleared in $CLIP_TIME seconds. - If a line-number is given, then only that line will be copied, displayed or printed from a multiline password. + If a line-selector is given, then only one line will be copied, displayed or printed from a multiline password. + If line-selector is a number, then it selects the Nth line; otherwise it is treated as a field name, and the first line with the matching field is selected. $PROGRAM grep [GREPOPTIONS] search-string Search for password files containing search-string when decrypted. $PROGRAM insert [--echo,-e | --multiline,-m] [--force,-f] pass-name @@ -398,7 +411,6 @@ cmd_show() { pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $? echo "$pass" | $BASE64 -d else - [[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location '$selected_line' is not a number." pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | extract_one_line "$selected_line")" || exit $? [[ $strip_field -eq 1 ]] && pass="${pass#*: }" [[ -n $pass ]] || die "There is no password at line ${selected_line}." diff --git a/tests/t0020-show-tests.sh b/tests/t0020-show-tests.sh index 9a7a0b4..7b895bb 100755 --- a/tests/t0020-show-tests.sh +++ b/tests/t0020-show-tests.sh @@ -66,4 +66,28 @@ test_expect_success 'Test "show --stdout" with out-of-range line-number' ' grep "There is no password at line 42" stderr ' +test_expect_success 'Test "show --stdout="' ' + echo "third: threethree" >expect && + "$PASS" show --stdout=third multiline >actual && + test_cmp expect actual +' + +test_expect_success 'Test "show --stdout= --strip-field"' ' + echo "fourfour" >expect && + "$PASS" show --stdout=fourth --strip-field multiline >actual && + test_cmp expect actual +' + +test_expect_success 'Test "show --stdout=" with partial match' ' + test_must_fail "$PASS" show --stdout=sec multiline >actual 2>stderr && + test ! -s actual && + grep "There is no password at line sec" stderr +' + +test_expect_success 'Test "show --stdout=" with non-existing field' ' + test_must_fail "$PASS" show --stdout=nope multiline >actual 2>stderr && + test ! -s actual && + grep "There is no password at line nope" stderr +' + test_done -- 2.51.0.663.g64752ac02f From szeder.dev at gmail.com Sat Sep 20 11:22:17 2025 From: szeder.dev at gmail.com (=?UTF-8?q?SZEDER=20G=C3=A1bor?=) Date: Sat, 20 Sep 2025 13:22:17 +0200 Subject: [PATCH 10/10] show: avoid errors due to SIGPIPE In-Reply-To: <20250920112217.3904292-1-szeder.dev@gmail.com> References: <20250920112217.3904292-1-szeder.dev@gmail.com> Message-ID: <20250920112217.3904292-11-szeder.dev@gmail.com> When outputting only a single line from a multiline password file, 'pass' might abort because of "broken pipe" error from 'gpg' if the password file contains relatively large amount of data after the selected line. To extract the Nth line from a multiline password file, 'pass' pipes the output from 'gpg' through a '... | tail -n +N | head -n 1' pipeline. Once 'head' gets and prints the first line, it exits immediately. If the previous stages still have enough data to output after that, they will get SIGPIPE and exit with a non-zero status ('tail' does so silently, while 'gpg' prints a few "broken pipe" error messages). Since 'pass' uses Bash's pipefail option, the whole pipeline gets a non-zero exit status, causing 'pass' to exit with the same non-zero status before it would output the password. Turning off pipefail for the pipeline extracting the selected line is not a good option, because that would hide not only the non-zero exit status due to SIGPIPE, but any other non-zero exit status that 'pass' should not ignore; and we would still need something to suppress the "broken pipe" error messages from 'gpg' (and only those error messages!). Instead, let's extend the last stage of that pipeline with a 'cat >/dev/null' command to always silently consume all leftover output from the previous pipeline stage after 'head' exited. The test added for this uses a password file close to 10x the size of the standard Linux pipe buffer size, so it should be reasonably reliable to trigger a SIGPIPE without this fix. The test also checks that 'pass' doesn't output anything to its standard error, just in case. --- src/password-store.sh | 4 +++- tests/t0020-show-tests.sh | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/password-store.sh b/src/password-store.sh index 3f673ef..505fc1b 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -161,7 +161,9 @@ extract_one_line() { else grep_gently "^$selected_line: " fi | - head -n 1 + # Make sure that the last pipeline stage reads the whole output of + # the previous stage to avoid SIGPIPE. + { head -n 1 ; cat >/dev/null ; } } # diff --git a/tests/t0020-show-tests.sh b/tests/t0020-show-tests.sh index 7b895bb..58a426b 100755 --- a/tests/t0020-show-tests.sh +++ b/tests/t0020-show-tests.sh @@ -90,4 +90,13 @@ test_expect_success 'Test "show --stdout=" with non-existing field' ' grep "There is no password at line nope" stderr ' +test_expect_success 'Huge password file should not lead to SIGPIPE' ' + echo 42 >expect && + seq 1 100000 >content && + "$PASS" insert -m huge actual 2>err && + test_cmp expect actual && + test ! -s err +' + test_done -- 2.51.0.663.g64752ac02f