Warning
pago is in beta. Using pago carries a greater risk of bugs, security vulnerabilities, and data loss than using mature software.
pago is a command-line password manager. It has the following built in:
- age public-key and password encryption
- Git version control of the password store (go-git)
- A fuzzy finder similar to fzf for choosing entries (go-fuzzyfinder)
- A multiline text editor for editing encrypted data without writing it to disk (tview)
pago encrypts passwords with one or more public keys using age (pronounced with a hard "g"). The public keys are called "recipients". Recipients can be:
- age recipients
- SSH public keys
A private key matching one of the recipient public keys can decrypt the password entry. The private keys are called "identities". Identities can be:
- age identities
- SSH private keys
The file with the identities is encrypted with a password, also using age.
pago implements an agent like ssh-agent or gpg-agent. The agent caches the identities. This means you don't have to enter the master password again during a session. pago starts the agent the first time you enter the master password. You can also start and stop the agent manually.
The pago password store format is compatible with passage. It has the following differences:
- The pago directory is located at
$XDG_DATA_HOME/pago/
, while passage uses~/.passage/
- passage supports an encrypted or an unencrypted identities file; pago only supports encrypted
An attacker who gets ahold of your pago directory but not the master password should be unable to access the passwords stored in pago except by brute-forcing the master password.
My primary password manager is KeePassXC. I use a secondary password manager to access a subset of secrets in cron jobs and scripts and on headless remote systems.
I used pass
for this for a time.
While I liked the design of pass
and found it pleasant to use, I didn't like setting up GPG on a new system.
I went looking for a pass
replacement based on age
because I had replaced GPG with age for encrypting files.
The following is the late-2024 shortlist of password managers I compiled before I decided to work on pago.
It includes explanations for why I didn't adopt them.
First, I needed the identities encrypted at rest and usable without reentering the password.
This ruled out passage, which had no agent, and pa, which didn't support encryption for the identities file.
kbs2 didn't integrate with Git.
seniorpw matched all of my criteria and was the closest to pass
.
It is what I would most likely be using if I didn't decide to develop my own.
The -k
/--key
feature in seniorpw later inspired TOML entries and the approach to TOTP.
All of the above password managers are worth your attention. For more options, see "Awesome age".
pago is a heavily modified fork of pash (archived). It has been ported from POSIX shell to Tcl to Go and from GPG to age.
You will need Go 1.24 or later to install pago. Once Go is installed on your system, run the following commands:
# pago and pago-agent are two separate binaries.
# You should install both unless you have a specific reason not to.
go install dbohdan.com/pago/cmd/...@latest
Shell completion files for Bash and fish are available in completions/
.
To install completions for fish, clone the repository and run install.fish
.
You may need to allow pago-agent to lock enough memory.
- pago is used by the developer on Linux, NetBSD, and (rarely) OpenBSD.
- pago is automatically tested on FreeBSD and macOS.
- pago does not build on Windows.
The pago agent and test suite don't work on Windows. Instead of offering a partial and untested Windows build, the project doesn't support Windows. Windows users interested in pago are encouraged to try it in WSL.
pago init
This will create a new password store, prompt you for a master password, and commit the recipients file to Git.
To use pago with an SSH key as an identity, follow these steps.
Back up your identities
file and install age for the command line before proceeding.
Note that the SSH key must not be encrypted, i.e., must not have a password.
If necessary, remove the password with ssh-keygen
.
pago encrypts identities
with a password using age encryption.
You may wish to work with secrets in memory or on an encrypted disk.
On Linux with glibc, you normally have /dev/shm/
available as temporary in-memory storage.
- Add your SSH public key to
.age-recipients
. You can have multiple recipients.
# Repeat for every SSH public key.
cat ~/.ssh/id_ed25519.pub >> ~/.local/share/pago/store/.age-recipients
# Re-encrypt the password entries to the new recipients.
pago rekey
- Add the corresponding SSH private keys to the encrypted identities file.
This is not automated and requires decrypting the file manually using the
age
command. We are going to use a directory in/dev/shm/
in this example.
# Edit the identities file.
mkdir -p "/dev/shm/pago-$USER-temp/"
age -d -o "/dev/shm/pago-$USER-temp/identities" ~/.local/share/pago/identities
# Repeat for every SSH private key.
# Ensure a line break.
echo >> "/dev/shm/pago-$USER-temp/identities"
cat ~/.ssh/id_ed25519 >> "/dev/shm/pago-$USER-temp/identities"
age -a -e -p -o ~/.local/share/pago/identities "/dev/shm/pago-$USER-temp/identities"
# Restart the agent to load the new identities.
pago agent restart
# Either generate or input a password.
pago add foo/bar
# Generate a random password.
pago add -r foo/bar
# Specify a custom length and character pattern (regular expression).
pago add -l 32 -p '[A-Za-z0-9#$%]' foo/bar
# Input your own password.
pago add -i foo/bar
# Read a multiline secret from stdin.
# This is useful for storing age keys or structured data.
# See the "TOML entries" section for more on the latter.
age-keygen | pago add -m foo/bar
Adding a password creates a Git commit by default.
# Show a password.
pago show foo/bar
# Copy to clipboard (clears after 30 seconds).
pago clip foo/bar
# Copy with a custom timeout (in seconds; 0 to disable).
pago clip -t 20 foo/bar
# List all entries organized in a tree.
pago show
# List entries with a name that matches a regular expression.
pago find fo
# Select an entry interactively using a fuzzy finder.
pago show --pick
# The same as `pago show --pick foo`. Starts the search with `foo`.
pago pick foo
# Edit a password that already exists.
pago edit foo/bar
# Create a password if it doesn't exist.
pago edit foo/new -f
# Edit without mouse support.
# This makes Termux automatically display the virtual keyboard
# when you tap the terminal.
pago edit --no-mouse foo/bar
# Generate a password without saving it.
pago generate
# Customize the length and pattern.
pago generate --length 16 --pattern '[a-z0-9]'
# Delete with confirmation.
pago delete foo/bar
# Force delete without confirmation.
pago delete -f foo/bar
# Pick what password to delete using a fuzzy finder.
pago delete -p foo/bar
By default, this will commit the deletion to Git.
# Rename an entry.
pago rename foo/bar foo/baz
# Move an entry to a new directory.
pago rename foo/baz qux/quux
This creates a Git commit by default.
pago can store and retrieve structured data in the TOML format. This is useful for storing multiple related values in a single entry, such as API keys, usernames, and URLs.
To create a TOML entry, use pago add --multiline
and provide TOML content on standard input.
The content must start with the string # TOML
.
pago add -m services/my-api <<EOF
# TOML
user = "jdoe"
password = "abcdef"
token = "tok-123"
url = "https://api.example.com"
numbers = [1, 1, 2, 3, 5]
EOF
When you show
or clip
a TOML entry without specifying a key, pago will use a default key.
The default key is password
.
You can specify a different default key by adding a key default
to the TOML entry.
pago add -m services/my-api-custom-default <<EOF
# TOML
default = "api-key"
api-key = "xyz-456"
EOF
pago show services/my-api-custom-default
# => xyz-456
You can retrieve other values from the TOML entry using the -k
/--key
option with show
, clip
, and pick
.
The option can be repeated to access nested keys.
To see all available keys (sorted), use the -K
/--keys
option with show
.
You can combine this with -k
/--key
to list keys within a nested table.
# List all keys in the entry.
pago show --keys services/my-api
# => numbers
# => password
# => token
# => url
# => user
# List all keys in a nested table.
pago show --keys -k table entry-with-table
# => key
# You can also pick an entry to list keys from.
pago show -K -p
# Show the user from the TOML entry.
pago show -k user services/my-api
# => jdoe
# Show a nested key.
pago show entry-with-table -k table -k key
# => value
# Show an array.
pago show -k numbers services/my-api
# => [1, 1, 2, 3, 5]
# Copy the key to the clipboard.
pago clip -k key services/my-api
When an entry is parsed as TOML, pago can retrieve scalar values (strings, numbers, booleans) and arrays of scalars. Arrays and scalars other than strings are encoded as TOML for output. pago cannot retrieve tables directly, but it can traverse them to access nested values.
pago can generate time-based one-time passwords (TOTP) from a TOML entry.
To use this feature, store the otpauth://
URI in a key named otp
.
The entry must start with # TOML
.
pago add -m services/my-service <<EOF
# TOML
user = "jdoe"
otp = "otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example"
EOF
When you use show
or clip
with the key otp
, pago will generate and output a TOTP code.
# Show the TOTP code.
pago show -k otp services/my-service
# => 123456
# Copy the TOTP code to the clipboard.
pago clip -k otp services/my-service
The agent keeps your identities in memory to avoid repeated password prompts. By default, the agent runs until manually stopped, but you can configure it to automatically expire after a period of inactivity.
# Start the agent automatically when needed.
pago show foo/bar
# Start manually.
pago agent start
# Start with automatic expiration after 1 hour of inactivity.
pago agent start --expire 1h
# By default, the agent locks its memory to prevent secrets from being written to swap.
# You may need to run the command `ulimit -l 100000` to let it lock enough memory.
# Alternatively, you can disable memory locking
# with the environment variable `PAGO_MEMLOCK=0` or the option `--no-memlock`.
pago agent start --no-memlock
# Run without an agent.
pago -s '' show foo/bar
# Shut down.
pago agent stop
pago-agent defaults to locking the process memory to prevent secrets from being written to swap. Secrets can be recovered from unencrypted swap that was not erased at system shutdown.
pago-agent uses up to 100 MiB of memory on systems where it has been tested.
Most operating systems don't allow a process to lock this much memory by default.
Additionally, on Free/Net/OpenBSD, the agent apparently needs the limit on locked memory to exceed its virtual memory even though only around 100 MiB is reserved.
The amount of virtual memory can be over 1 GiB.
(You don't lose 1 GiB of memory.)
Configure your system to allow this or set the environment variable PAGO_MEMLOCK=0
to disable locking.
Here is how to allow users to lock more memory on different operating systems. In these examples, we set the limit to 8 GiB.
- Create
/etc/systemd/system.conf.d/
if it doesn't exist. - Edit
/etc/systemd/system.conf.d/limits.conf
to contain the following:
[Manager]
DefaultLimitMEMLOCK=8G
- Restart your user session.
- Edit
/etc/security/limits.conf
and add this line:
* hard memlock 8589934592
- Restart your user session.
- Edit
/etc/login.conf
and update the default value ofmemorylocked
:
default:\
[...]
:memorylocked=8G:\
[...]
- On FreeBSD only, run the following command as root:
cap_mkdb /etc/login.conf
- Restart your user session.
PAGO_AGENT
: The agent executable. Default:pago-agent
.PAGO_CLIP
: The command to use to copy text to the clipboard. When empty (default), the command is determined automatically using atotto/clipboard.PAGO_CONFIRM
: Whether to ask for a new password twice for confirmation.0
to disable. Default:1
(enabled).PAGO_DIR
: The pago data directory location. Defaults to:- Linux and BSD:
~/.local/share/pago
- macOS:
~/Library/Application Support/pago
- Linux and BSD:
PAGO_EXPIRE
: Agent expiration time after which it will automatically shut down (Go duration string; for example,1h30m
). Default: no expiration.PAGO_GIT
: Whether to use Git.0
to disable. Default:1
(enabled).PAGO_LENGTH
: The default length of random passwords. Default:20
.PAGO_MEMLOCK
: Whether the agent should lock its memory using mlockall(2) to prevent secrets from being written to swap.0
to disable. Default:1
(enabled).PAGO_MOUSE
: Whether to enable mouse support in the interactive editor.0
to disable. Default:1
(enabled).PAGO_PATTERN
: The default character pattern (regular expression) for random passwords. Default:[A-Za-z0-9]
.PAGO_SOCK
: The agent socket path. The default path is determined by trying candidate paths until the parent directory of the pago directory exists.${FOO:-default value}
withFOO
in capital letters indicates an environment variable;${foo}
indicates an internal pago variable.- Linux and BSD:
${XDG_RUNTIME_DIR}/pago/socket
/var/run/xdg/${username}/pago/socket
on FreeBSD only/run/user/${uid}/pago/socket
/var/run/user/${uid}/pago/socket
${TMPDIR:-/tmp}/pago-${username}@${hostname}/socket
- macOS:
${TMPDIR:-/tmp}/pago-${username}@${hostname}/socket
- Linux and BSD:
PAGO_TIMEOUT
: The default timeout to clear the clipboard. Default:30
(seconds).
The editor is implemented as a text area from the tview library. It has the following key bindings:
- Ctrl+C: Exit without saving
- Ctrl+D: Save and exit
- ←: Move left one character
- →: Move right one character
- ↑: Move up one row
- ↓: Move down one row
- Home/Ctrl+A: Move to the start of the line
- End/Ctrl+E: Move to the end of the line
- PgUp/Ctrl+B: Page up
- PgDn/Ctrl+F: Page down
- Ctrl+←/Alt+B: Move to the start of the word
- Ctrl+→/Alt-F: Move to the end of the word
- Ctrl+Home: Move to the start of the text
- Ctrl+End: Move to the end of the text
- Enter: Insert newline
- Tab: Insert tab (
\t
) - Backspace/Ctrl+H: Delete the previous character
- Delete: Delete the next character
- Alt+Backspace: Delete the previous word
- Ctrl+W: Delete back to the start of the word
- Ctrl+K: Delete to the end of the line
- Ctrl+U: Delete the entire line
- Shift + navigation key: Extend selection
- Ctrl+L: Select the entire text
- Mouse drag: Select text
- Left double-click: Select a word
The editor clipboard is synchronized with the system clipboard.
- Ctrl+Q: Copy selected text
- Ctrl+X: Cut selected text
- Ctrl+V: Paste from the clipboard
- Ctrl+Z: Undo
- Ctrl+Y: Redo
MIT.
See the file LICENSE
.