A command line secrets manager for Linux and macOS. Secrets are organised into
encrypted vaults: one file each, edited in $EDITOR, searched with regular
expressions.
Archives are published on the
releases page: one per tagged
version, plus a dev release rebuilt on every push to main.
| Platform | Asset |
|---|---|
| Linux x86_64 | mrs_linux_x86_64.tar.gz |
| Linux arm64 | mrs_linux_arm64.tar.gz |
| macOS Apple Silicon | mrs_darwin_arm64.tar.gz |
The archive carries LICENSE and README.md alongside the binary, so name the
binary rather than extracting everything into the current directory:
tar -xzf mrs_linux_x86_64.tar.gz mrs
sudo install -m 755 mrs /usr/local/bin/mrsTo compile from source, with Go and Make:
git clone https://github.com/andornaut/mrs.git
cd mrs
make install- A vault holds secrets separated by blank lines.
- The first line of a secret is its key; the rest is its value.
- Every line is kept as typed: indentation, trailing spaces, and lines that
begin with a
#. - Secrets are sorted by key, ignoring case, when saved. Two may share a key, and
mrswarns when they do. mrs addandmrs editopen$EDITORon three instruction lines, which are removed on save wherever they end up in the buffer.
Commands that read or write the secrets in a vault name it with --vault,
which accepts a prefix. Commands that create, rename or destroy a vault take
its whole name as an argument.
| Command | Does |
|---|---|
mrs add |
Add secrets in an editor |
mrs edit |
Edit secrets in an editor |
mrs search <regular expression>... |
Print matching secrets |
mrs export |
Print every secret |
mrs vault list |
Print vault names |
mrs vault default |
Print the default vault |
mrs vault create <name> |
Create a vault |
mrs vault change-password <name> |
Re-encrypt under a new password |
mrs vault rename <source> <target> |
Rename a vault |
mrs vault delete <name> |
Delete a vault, after confirming |
search matches keys only, unless --full. Matching is case insensitive, and
arguments are joined, so mrs search bank account matches bank account.
mrs --version prints the version, and -h, --help works on every command.
| Flag | Commands | Supplies |
|---|---|---|
-v, --vault |
add, edit, search, export |
the vault's name, or the start of it |
-p, --password-file |
add, edit, search, export, vault create, vault change-password |
the vault's current password |
-n, --new-password-file |
vault change-password |
the password to change it to |
-i, --import-file |
vault create |
unencrypted secrets to seed the vault with |
-f, --full |
search |
match values as well as keys |
-y, --yes |
edit, vault delete |
the answer to the confirmation |
--force |
add, edit, vault create, vault change-password, vault delete, vault rename |
permission to delete another process's lock file |
--path |
vault list, vault default |
paths instead of names |
A short flag means the same thing on every command. --force and --path have
no short form, because both are worth spelling out.
add, edit, search and export name a vault with -v, which takes a
prefix. An exact name always wins, whatever longer names begin with it: with
work and work-archive, -v work is work. Short of one, a prefix has to
fit exactly one vault:
$ mrs edit -v alph
Error: "alph" begins the name of 2 vaults: alpha, alphabet. Use the whole name of the one you mean
Without -v, those four use $MRS_DEFAULT_VAULT_NAME, or the only vault if
there is just one. Unlike -v, the configured name has to match exactly. No
vaults, or several with nothing configured, is an error rather than a guess.
vault create, vault change-password, vault rename and vault delete name
the vault as an argument instead, and take no prefix at all: each one creates,
re-keys, moves or destroys a vault, so a name short of the whole thing must not
reach a neighbouring one. They name the closest vault when given a prefix:
$ mrs vault delete alph
Error: vault "alph" not found. Did you mean "alpha"?
Names may hold ASCII letters, digits, _ and -, up to 200 characters.
- Prompted on the terminal with echo off, and at least 8 characters long.
- Without a terminal there is nothing to prompt from, so pass
--password-file. A trailing newline is trimmed, soecho 'pw' > pwworks; other whitespace is part of the password. - Every save first copies the vault to
<name>.<salt>.bak. Aftervault change-passwordthat backup still opens with the old password until the next save, so delete it if that password is no longer trusted.
mrs edit that would empty a vault, and mrs vault delete, ask first.
-y, --yes answers in advance. Without a terminal and without --yes, mrs
fails rather than assume an answer:
$ mrs vault delete old < /dev/null
Error: cannot ask "Delete vault old?": stdin is not a terminal. Use --yes to answer it
stdout carries what a caller consumes: vault names from vault list and
vault default, secrets from export and search. Prompts, warnings, errors
and reports go to stderr, so mrs export > secrets and mrs search key | less
carry the secrets alone.
$ mrs search bar
Vault password:
1 secret matched "bar" in vault example
another secret key bar
bank account number: 1234
| Code | Meaning |
|---|---|
| 0 | it worked |
| 1 | it failed |
| 2 | it was typed wrong: no command, an unknown command or flag, or a missing or extra argument |
| 3 | mrs search ran and matched nothing |
| 128+n | a signal ended it: 129 SIGHUP, 130 SIGINT, 131 SIGQUIT, 143 SIGTERM |
A wrong invocation prints the usage that would have been right; a command that
ran and failed does not. mrs --help writes help to stdout and reports success.
| Path | Holds |
|---|---|
$MRS_HOME/vaults/<name>.<salt> |
the vault, mode 0600 |
$MRS_HOME/vaults/<name>.<salt>.bak |
the version before the last save |
$MRS_HOME/vaults/<name>.lock |
the write lock, empty |
$MRS_TEMP/mrs/<run>/ |
decrypted secrets while an editor is open, mode 0700 |
The vault directory is mode 0700. mrs narrows permissions it finds wider than
that and never widens them. The temporary directory is removed when mrs exits,
including on SIGHUP, SIGINT, SIGQUIT and SIGTERM.
| Environment variable | Description |
|---|---|
EDITOR |
The editor add and edit open (default: nano). May carry arguments, such as vim -n. Quote a path that contains spaces. |
MRS_DEFAULT_VAULT_NAME |
The vault to use when --vault is not given. Must name one exactly (default: the only vault, if there is just one). |
MRS_HIDE_EDITOR_INSTRUCTIONS |
If set to any value, omit the instruction lines from editor sessions. |
MRS_HOME |
Where vaults are stored (default: $XDG_DATA_HOME/mrs, else $HOME/.local/share/mrs). |
MRS_TEMP |
Where decrypted secrets are written while an editor is open (default: $XDG_RUNTIME_DIR, else the system temporary directory). |
- 256-bit AES-GCM.
- PBKDF2-SHA256, 600,000 iterations, over a 32 character salt that is unique per vault and carried in its filename.
- Vaults written with the earlier 4,096 iterations are still read, and are re-encrypted at 600,000 on the next save.
The AES-GCM seal and open in internal/crypto
are copied from cryptopasta, which its
author placed in the public domain under CC0 to be copied rather than imported.
The ciphertext is nonce|ciphertext|tag with a random 96-bit nonce per save.
See the Makefile. make test runs both layers:
internal/holds unit tests, beside the code they exercise.test/e2edrives a compiledmrsagainst real vault files, a real editor process and real encryption. Nothing is mocked. Each test gets its own vault, temporary and home directories, so they run in parallel; the fake editor is scripted through the environment, and the cases that answer a prompt runmrson a pseudo-terminal, because without one it reports that it cannot ask.
To release, push a semantic version tag from main:
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0The release workflow runs the tests, then
builds and publishes the binaries with GoReleaser.
Every push to main republishes the rolling dev release the same way.