Use Cache of Emacs Auth-Source Interface to Password-Store as Alternative to SSH-Agent for Magit?

Let's say you use Git and sign commits with an SSH key. Your SSH key may have a password you may not want to type in every time you commit. You'd usually launch an SSH-agent. (I guess the same applies to the case when you use a GPG key to sign commits. You'd then spawn a GPG-agent.)

If you use GNU Emacs, Magit as interface to Git, and Auth-Source as interface to Password-Store, you can use the cache of Auth-Source to avoid repeating your password for each commit. Is this a safe from an information-security point of view? Let me know in a comment in Fediverse or per email.

Update: wasamasa explains how the setup described here indeed imposes a security risk. Please check out the comment on Fediverse and the linked article on emacshorrors.com.

Here's how to set it up: Use different SSH keys for different platforms and safe them all in the same directory:

/home/user/.ssh/keys/
├── codeberg
├── codeberg.pub
├── github
└── github.pub

For each of these SSH keys, add an entry to Password-Store with an additional .ssh suffix:

$ pass
Password Store
├── codeberg.ssh
└── github.ssh

Configure Git (repositories) to use one of those SSH keys to sign commits; e.g. for a Github-hosted Git repository that you contribute to:

[commit]
gpgsign = true

[gpg]
format = ssh

[user]
signingkey = /home/user/.ssh/keys/github

In your Emacs initialization file, make Magit recognize SSH password prompts. Let's first take a look at this docstring:

magit-process-find-password-functions is a variable defined in `magit-process.el'.

List of functions to try in sequence to get a password.

These functions may be called when git asks for a password, which is detected using `magit-process-password-prompt-regexps'. They are called if and only if matching the prompt resulted in the value of the 99th submatch to be non-nil. Therefore users can control for which prompts these functions should be called by putting the host name in the 99th submatch, or not.

Accordingly, we teach Magit to also match the name of the key as 99th submatch:

(require 'magit-process)
(require 'rx)

(add-to-list
   'magit-process-password-prompt-regexps
   (rx line-start
       "Enter passphrase for "
       "\"/home/user/.ssh/keys/"
       (submatch-n 99
         (one-or-more (not (any "\""))))
       "\": "
       line-end))

(add-to-list
   'magit-process-password-prompt-regexps
   (rx line-start
       "Enter passphrase for key "
       "'/home/user/.ssh/keys/"
       (submatch-n 99
         (one-or-more (not (any "'"))))
       "': "
       line-end))

Now we need to customize the user option magit-process-find-password-functions so that the password for the relevant SSH key (e.g. codeberg or github, that had been recognized as 99th submatch from one of above regular expressions) is correctly queried from Password-Store. Write an Elisp function and add its symbol to the mentioned variable: (Note that for some setups you might be able to use magit-process-password-auth-source instead of a new Elisp function.)

(defun magit-process-find-password (key)
  (when-let*
      ((secret (auth-source-search
                :max 1
                :host (concat key ".ssh")))
       (secret (plist-get (car secret)
                          :secret)))
    (if (functionp secret)
        (funcall secret)
      secret)))

(setopt magit-process-find-password-functions
        (list #'magit-process-find-password))

When you now attempt a Git commit through Magit, you will be asked for the password of your Password-Store in the minibuffer. (I assume you set epg-pinentry-mode variable to 'loopback like a sane Emacs user.) If you attempt to a Git commit again soon enough, you'll hit the cache of Auth-Source and thus won't be prompted for a password. Sometimes you might want to clear the cache manually with M-x auth-source-forget-all-cached RET. Good luck.