ADR 0013
A keyring unlocked at login, and an SSH agent that forgets
Context
Nothing in an Alpymist session kept secrets. Programs that store a password
or a token ask the Secret Service (org.freedesktop.secrets) for somewhere to
put it, and there was none. What they do then differs, and none of it is
good:
- Chromium and every Electron app (VS Code among them) choose where they
keep secrets by which desktop they believe they are on. Hyprland is not one
they know, and on a desktop they do not know they use
basic_text: a file encrypted with a key built into the program, which is to say in the clear. VS Code first offers "Use weaker encryption", and on the dev VM that had been clicked: its~/.vscode/argv.jsonsaid"password-store": "basic". - libsecret apps fail to save, or ask every time.
- git and ssh asked for a key's passphrase on every connection, since no agent ran.
ADR 0011 says no secret is kept in the clear by default. The first two are exactly that, by default, the moment anyone signs in to anything.
Decision
gnome-keyring is the Secret Service, unlocked at login. It is what
libsecret, VS Code and Flatpak's Secret portal are tested against. Alpine's
base PAM stack already calls pam_gnome_keyring wherever the module is
installed: auth hands it the password typed at the login screen, session … auto_start starts the daemon, and password … use_authtok changes its
password when the login's changes. So alpymist-desktop depends on
gnome-keyring and gnome-keyring-pam, and writes no PAM configuration of
its own. The keyring is encrypted with the login password, on disk in
~/.local/share/keyrings.
PAM runs before the session's bus exists; dbus-run-session makes that bus
around alpymist session. So alpymist session runs
gnome-keyring-daemon --start --components=secrets, which puts the daemon PAM
started, already unlocked, on that bus. The Secret portal is gnome-keyring's
(hyprland-portals.conf), for sandboxed apps that use it rather than the bus.
Chromium and Electron are told they are on GNOME's keyring by
DESKTOP_SESSION=gnome, set by alpymist session for the whole session.
They read it only when XDG_CURRENT_DESKTOP names a desktop they do not know,
as Hyprland is to them; tried on the dev VM, VS Code's Flatpak went from
basic_text to gnome_libsecret with nothing else changed. Setting
XDG_CURRENT_DESKTOP=Hyprland:GNOME would do the same, but far more programs
read that variable and would start behaving as if on GNOME; nothing is
configured app by app, which would never keep up. An app someone already told
to use basic — as the dev VM's VS Code was — keeps doing so until that line
is taken out of its own configuration.
git keeps HTTPS credentials there too: /etc/gitconfig, which no Alpine
package owns, sets credential.helper to libsecret, and the desktop brings
git-credential-libsecret. An account's own ~/.gitconfig can name another
helper. gh stores its token in the Secret Service by itself once there is
one.
The login screen does not get a keyring. greetd runs it as its own
greetd account, through the same PAM stack, so base-session started a
keyring daemon for that account too, before anyone logged in. The greeter has
its own PAM service, alpymist-greeter, set per session in greetd's
configuration: the runtime directory and the keyboard layout a login gets, and
base-session-noninteractive, which starts nothing else.
passwd is shadow's, which goes through PAM. busybox's writes
/etc/shadow itself, so the keyring would keep the old password and ask for
it at the next login. Settings' password button runs passwd, and gets
shadow's.
The SSH agent is OpenSSH's ssh-agent, the one that keeps nothing. It
holds a key only in memory, refuses to be traced or dumped, and stores no
passphrase anywhere. gcr's agent would remember passphrases in the keyring, so
a key would unlock itself at every login after the first. That is more
convenient, and it puts every key's passphrase on disk behind the login
password. So:
alpymist sessionstarts the compositor underssh-agent -a $XDG_RUNTIME_DIR/ssh-agent.socket. Given a command, the agent runs it and exits when it does, so the keys end with the session./etc/ssh/ssh_config.d/alpymist.confsaysAddKeysToAgent yes: a key's passphrase is asked the first time it is used in a session, and not again until the session ends or the screen locks.- The passphrase is asked in OpenSSH's own dialog, from
openssh-askpass(SSH_ASKPASS, withSSH_ASKPASS_REQUIRE=prefer), from a terminal as from an editor: one way of being asked. gcr'sgcr4-ssh-askpass, chosen at first, refuses to run as anything but gcr's own agent's helper; found on the dev VM, where no dialog appeared. The dialog is GTK's, not Alpymist's, and Ctrl+Alt+Delete cannot vouch for it the way it does foralpymist-auth's prompt; an askpass of Alpymist's own is left for later. - The lock screen runs
ssh-add -Donce the screen is covered, so a machine left locked has no key unlocked.
Consequences
- VS Code, browsers and libsecret apps keep sign-ins in the keyring, which the
login unlocks. A secret saved before this in
basicmode is not moved: the app asks to sign in again, or keeps usingbasicif told to. - An account has no keyring until its first login after this: PAM makes the login keyring then, with the password just typed, so it starts in step.
- Every SSH key's passphrase is asked once per session and again after each
lock.
AddKeysToAgent confirmin~/.ssh/configasks before every use too, for whoever wants that. - gnome-keyring's own autostart entries are GNOME's only (
OnlyShowIn), soalpymist autostartleaves them alone and nothing starts it twice.
Addendum — 2026-09-29: the passphrase is asked in Alpymist's own dialog
openssh-askpass's GTK dialog is gone. alpymist-auth asks instead, linked
as /usr/libexec/alpymist-askpass and set as SSH_ASKPASS. ssh runs the
program it names with the question as its only argument, so the link's name is
what tells alpymist-auth it is being run as an askpass; alpymist-auth askpass QUESTION is the same thing by hand.
It is the polkit prompt's layer, backdrop and exclusive keyboard, drawn under the same name by the same binary, so Ctrl+Alt+Delete vouches for a passphrase prompt as it does for a password one. A lookalike asking for a key's passphrase is now told apart, which with the GTK dialog it could not be.
What ssh asks is shown in ssh's words. SSH_ASKPASS_PROMPT=confirm (ssh-add -c, AddKeysToAgent confirm) is Allow or Deny, with Deny focused, since the
dialog takes the keyboard and a keystroke meant for somewhere else must not
allow a key. SSH_ASKPASS_PROMPT=none only shows its message. A username, or
the yes-or-no of a host key never seen before, is shown as it is typed; a
passphrase, a PIN or a password is dots.
The passphrase goes to ssh on standard output and nowhere else. It is held in the prompt's self-wiping buffer until then.