Skip to content

Repository files navigation

Mr. Secretary (mrs)

Test License: MIT

A command line secrets manager for Linux and macOS. Secrets are organised into encrypted vaults: one file each, edited in $VISUAL or $EDITOR, searched with regular expressions.

Installation

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/mrs

To compile from source, with Go and Make:

git clone https://github.com/andornaut/mrs.git
cd mrs
make install

Secrets

  • A vault holds secrets separated by blank lines. A line of nothing but spaces or tabs separates them too, and is written back as a blank one.
  • The first line of a secret is its key; the rest is its value.
  • Every other 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 mrs warns when they do, on import as well as on save.
  • A file given to vault add --import-file is stored as it is written, so it keeps its own order until the vault is next saved.
  • mrs add and mrs edit open $VISUAL or $EDITOR on the secrets alone, and encrypt whatever the editor saves. mrs add --help states the format.

Commands

Commands that read or write the secrets in a vault name it with --vault, which accepts a prefix, or with --file, which names a vault file wherever it is kept. 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 ls Print vault names
mrs vault default Print the default vault
mrs vault add <name> Add a vault
mrs vault change-password <name> Re-encrypt under a new password
mrs vault rename <source-name> <target-name> Rename a vault
mrs vault rm <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. vault ls prints names sorted ignoring case, as secrets are sorted by key. mrs --version prints the version, and -h, --help works on every command.

Flags

Flag Commands Supplies
-v, --vault add, edit, search, export the vault's name, or the start of it
--file add, edit, search, export the path of a vault file, instead of a name
-p, --password-file add, edit, search, export, vault add, vault change-password a file holding the vault's current password
-n, --new-password-file vault change-password a file holding the password to change it to
-i, --import-file vault add a file of unencrypted secrets to seed the vault with
-f, --full search match values as well as keys
-y, --yes edit, vault rm the answer to the confirmation
--force add, edit, vault add, vault change-password, vault rm, vault rename permission to repair a lock file that cannot be used
--path vault ls, vault default paths instead of names

A short flag means the same thing on every command. --file, --force and --path have no short form, because -p is the password file wherever there is one, -f is --full on search, and --force is worth spelling out. --path takes no value: it asks vault ls and vault default to print paths rather than names.

--force repairs a lock file that cannot be opened, because its mode forbids it, a directory sits in its place, or it is a symlink that does not resolve. It never takes a lock another process holds: taking one would mean deleting the lock file, leaving the two processes holding two different files. A held lock is refused with or without the flag:

$ mrs vault rm work --force --yes
Error: vault work is currently locked by another process. --force repairs a lock
file that cannot be used, and does not take a lock another process holds

Wait for the other process, or stop it. A lock file left behind by a process that died needs neither: it is already re-lockable.

Naming a vault

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 add, vault change-password, vault rename and vault rm 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 rm alph
Error: vault "alph" not found. Did you mean "alpha"?

Names may hold ASCII letters, digits, _ and -, up to 200 characters.

A vault kept elsewhere

add, edit, search and export also take --file, which names a vault file directly: one on removable media, or in a directory that is synced elsewhere. Nothing is looked up, so it names no prefix and falls back to no default, and it is refused alongside -v:

$ mrs search --file /mnt/usb/work.<salt> aws
Vault password:
1 secret matched "aws" in vault /mnt/usb/work.<salt>

The file still has to be named <name>.<salt>, because the key is derived from the salt the filename carries and there is nowhere else to read it from. Copy or move a vault with its salt intact and it opens with the password it always had. Its lock file and the temporary file of a save are its siblings, so mrs writes in the directory the vault is in and needs to be able to write there. A vault outside the vault directory is not listed by vault ls, and $MRS_DEFAULT_VAULT_NAME cannot name one.

Passwords

  • Prompted on the terminal with echo off. At least 8 characters, and no newline. A password mrs will not accept is refused before it asks you to confirm it.
  • Without a terminal there is nothing to prompt from, so pass --password-file. A trailing newline is trimmed, so echo 'pw' > pw works; other whitespace is part of the password.
  • A save replaces the vault and writes no copy of it. Nothing beside a vault goes on opening with a password it no longer has, and nothing is a way back from an edit: keep your own copy if you want one.

Confirmations

mrs edit that would empty a vault, and mrs vault rm, ask first. -y, --yes answers in advance. Without a terminal and without --yes, mrs fails rather than assume an answer:

$ mrs vault rm old < /dev/null
Error: cannot ask "Delete vault old?": stdin is not a terminal. Use --yes to answer it

Output and exit codes

stdout carries what a caller consumes: vault names from vault ls and vault default, secrets from export and search. Warnings, errors and reports go to stderr, so mrs export > secrets and mrs search key | less carry the secrets alone. Prompts go to the terminal itself, so that redirecting stderr does not leave mrs waiting for an answer nobody can see.

$ 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.

Files

Path Holds
$MRS_HOME/vaults/<name>.<salt> the vault, mode 0600
$MRS_HOME/vaults/<name>.lock the lock on the name, 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.
  • One process at a time may write a vault or claim its name. Reads take no lock, and every write is atomic, so a reader never sees a half-written vault.
  • A lock file outlives the vault it is named for, because removing it would leave two processes holding two different files.
  • The temporary directory is created only when secrets are decrypted, and removed when mrs exits, including on SIGHUP, SIGINT, SIGQUIT and SIGTERM. A SIGKILL or a power loss leaves the decrypted file behind, because nothing runs to remove it and no later run sweeps it up; delete it by hand.

A file in the vault directory that is not shaped like a vault is named on stderr and otherwise left alone. A vault that mrs cannot read is a different thing: a symlink whose target is not there, a directory where a file should be, or a vault written by a release that derived its key differently. Those are listed with a warning saying why, keep their names, and can be renamed or deleted like any other; only the commands that have to read them fail.

Configuration

Environment variable Description
EDITOR The editor add and edit open, if $VISUAL is unset (default: the first of vim, vi, nano that is on PATH). 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_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).
VISUAL The editor add and edit open, in preference to $EDITOR. Same form.

Encryption

  • 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 not read. mrs says so when it meets one, rather than reporting the wrong password it cannot tell it from. Every release up to v0.1.7 reads one and re-encrypts it at 600,000 iterations on the next save, so open and save such a vault with one of those.

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.

Developing

See the Makefile. make test runs both layers:

  • internal/ holds unit tests, beside the code they exercise.
  • test/e2e drives a compiled mrs against 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 run mrs on 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.0

The test workflow runs the tests and golangci-lint on every branch and pull request. The release workflow calls it, then builds and publishes the binaries with GoReleaser. Every push to main republishes the rolling dev release the same way.

About

Mr. Secretary - Organise and secure your secrets

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages