A suckless evaluation of pass, GPG-agent, and alternatives

Verdict

The Suckless project does not prescribe a password manager or a key-management daemon. Its official position is a design philosophy: simplicity, clarity, frugality, small scope, and Unix composition.

pass is nevertheless a strong fit. Its own author describes it as a simple Unix password manager: encrypted files, ordinary filesystem tools, shell commands, and optional Git synchronization. Its design deliberately uses GPG and the standard gpg-agent.

For this repository, keep pass and GPG. Do not run a separate GPG service under runit. Simplify the integration instead: configure the terminal once in interactive shell startup and let GnuPG start its agent on demand.

age/passage is the credible alternative if GPG’s key model and agent/pinentry machinery are an unacceptable source of complexity. It requires a new store or migration and makes a different security tradeoff; it is not an automatic fix for this existing store.

What Suckless actually says

The official Suckless philosophy says software should focus on “simplicity, clarity and frugality” and describes code reduction and Unix philosophy as virtues:

https://suckless.org/philosophy/

The official tools catalogue contains small utilities such as dmenu, slock, ii, and quark, but no official password manager or GPG-agent policy:

https://tools.suckless.org/

Therefore claims such as “Suckless recommends GPG” or “Suckless rejects daemons” would be invented. The reasonable method is to apply the principles to the actual threat model and workflow.

Is pass aligned with that philosophy?

Very directly. The pass project says:

Source: https://www.passwordstore.org/

This is closer to the Suckless ideal than a cloud account, browser integration stack, database vault, or large desktop application. The use of an agent does not by itself make the design non-suckless: it is a small shared Unix service that avoids making every client implement private-key handling and passphrase caching.

The important distinction is that gpg-agent is not an additional password manager. It is GnuPG’s backend for private-key operations and passphrase caching.

What GnuPG itself recommends

The official GnuPG manual says that gpg-agent is automatically started on demand by gpg, gpgsm, gpgconf, or gpg-connect-agent; there is normally no reason to start it manually.

It also recommends setting this in the shell initialization used by all shell invocations:

GPG_TTY=$(tty)
export GPG_TTY

The same manual documents updatestartuptty, especially for switching the terminal/display used by GPG’s SSH-agent support.

Source: https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html

This changes the conclusion about the current workaround:

The current setup/neon-usage-env.sh performs all three commands locally before reading the secret. It works, but it puts session repair logic in an application-specific generator. A simpler design is to set GPG_TTY once in the common interactive shell environment and let pass invoke GPG normally.

A POSIX-safe form that does nothing when there is no terminal is:

if GPG_TTY=$(tty 2>/dev/null); then
    export GPG_TTY
fi

This belongs in the shared shell initialization, not in a Neon-specific script. Keep updatestartuptty as a manual recovery command, or use it when changing terminals if SSH-agent support is enabled.

The main alternative: age and passage

age is explicitly designed as a simple Unix encryption tool. Its project describes it as having small explicit keys, no configuration options, and Unix-style composability:

https://age-encryption.org/

passage is a fork of pass that replaces GPG with age:

https://github.com/FiloSottile/passage

This removes the GPG-specific keyring, agent socket, GPG configuration, and GPG_TTY problem. The basic model is easier to explain:

passage -> age -> identity file -> decrypted text

There are two relevant security choices:

  1. Unencrypted age identity on disk: simplest and usually silent. Anyone who can read the identity file can decrypt the store. The passage documentation says this can be reasonable when access to the local disk already implies access to the store.
  2. Passphrase-encrypted age identity: better protection against an offline disk copy, but it requires a passphrase prompt. Without an agent cache, repeated commands may prompt repeatedly; age can use pinentry when available or fall back to the CLI.

So passage is simpler operationally, but “no agent” does not automatically mean “more secure.” It shifts the choice to where the identity is stored and how often it is unlocked.

It is also not a drop-in backend for this repository. Existing pass/neon-usage.gpg entries need migration to .age entries, and scripts, extensions, and recovery documentation need updating.

Other options considered

gopass

gopass is a more capable replacement for pass. Its own project describes support for GPG, age, Git, other storage backends, teams, browser integration, and more:

https://github.com/gopasspw/gopass

That may be useful for teams, but it adds features and a larger implementation. It is not the minimal choice for this one-user dotfiles repository.

KeePass, Bitwarden, and similar vaults

These can be excellent security products, particularly where multi-device sharing, recovery, auditing, or browser integration matters. They introduce a database/application or hosted-service model, however, so they do not follow the same small-files-plus-Unix-tools approach. Security requirements can justify that complexity, but “Suckless” alone is not a reason to choose one.

A custom shell wrapper around age

This could be the smallest implementation for a few secrets, but it would duplicate indexing, editing, generation, synchronization, permissions, and migration behavior already provided by pass or passage. It is less code only until the missing edge cases matter.

Recommendation for this dotfiles repository

Keep

  • pass and the existing GPG-encrypted Git store;
  • /usr/bin/pinentry-curses for terminal prompts;
  • the current GPG key migration and recovery process;
  • the existing application runit services.

Simplify

  • Put GPG_TTY=$(tty) in the common interactive shell initialization.
  • Remove the explicit gpgconf --launch gpg-agent from the Neon-specific path unless it is retained as a deliberate workaround.
  • Do not call updatestartuptty before every mk neon; use it for a stale-terminal recovery or GPG SSH-agent setup.
  • Let pass show pass/neon-usage be the end-to-end health check. It already starts the agent when needed.

Consider passage only when

  • GPG’s compatibility and existing store are no longer valuable;
  • you accept a migration;
  • you have chosen deliberately between a plain identity file and a passphrase-encrypted identity;
  • you want the smaller age model more than the mature GPG ecosystem.

Final answer to the original question

People closest to the Unix/suckless style do not have one mandatory answer. The strongest evidence points in two directions:

For the current machine and existing encrypted store, replacing GPG because of one TTY failure would be complexity added for the wrong reason. Keep GPG, configure GPG_TTY once, and remove unnecessary lifecycle management from mk neon.

Sources