A local, offline CLI password manager written in Rust. Vault stores your credentials in encrypted files on disk using AES-256-GCM and derives keys with Argon2id — so your master password never leaves your machine.
- AES-256-GCM authenticated encryption
- Argon2id key derivation (memory-hard, resistant to brute force): 128 MiB and 3 passes, recorded in each vault so the cost can grow without breaking old vaults
- Multiple named vaults — one per context (work, personal, etc.)
- Tab-completion of commands, entry names, vault names and file paths, and command history in the interactive shell
- Interactive selector (
find): browse your entries with the arrow keys and fuzzy search - Notes and password history on each entry: the last 10 passwords are kept, with the date each one was replaced
- Auto-lock: an unlocked vault left alone at the prompt for 5 minutes locks itself (with uncommitted changes it only warns)
- Health report (
health): reused and short passwords, and the oldest ones - Password generator: random passwords (optionally without symbols or look-alike characters) and passphrases from the EFF word list, with their strength in bits
- Careful clipboard:
copykeeps the password out of clipboard managers' history and clears it after 30 s - Commit model: changes stay pending until
commit;statuslists them,discarddrops them, andrestorebrings back the version the last commit replaced - Crash-safe saves: every commit writes a new file and atomically swaps it in, keeping the previous version as a backup
- Safe across sessions: open the same vault in two terminals; a commit never overwrites what the other one saved
- Strict Security: Memory zeroization of the key and every secret (best effort: see the known gaps), no core dumps, no debugging or memory reads by your other programs (Linux), owner-only permissions on Unix (
0700directory;0600vaults, backups and exports), and path traversal protection. - Zero network access — entirely offline
- Rust 1.89 or newer (edition 2024), only to build from source
- Linux/Unix:
copyneeds a graphical session, X11 or XWayland under Wayland (arboardtalks to X11, with no system libraries to install). Without one, everything else works andcopyreports the clipboard as unavailable.
A ready binary (Linux x86_64, static: no dependencies): download vault-v0.1.0-x86_64-linux.tar.gz and SHA256SUMS from the latest release, then
sha256sum -c SHA256SUMS
tar -xzf vault-v0.1.0-x86_64-linux.tar.gz
install vault-v0.1.0-x86_64-linux/vault ~/.local/bin/From source, with Rust 1.89 or newer:
cargo install --git https://github.com/vortex2jm/vault --tag v0.1.0Or from a clone:
git clone https://github.com/vortex2jm/vault
cd vault
cargo install --path .Releases and what changed in each: CHANGELOG.md; how they are made: RELEASING.md.
--- Vault CLI ---
vault[locked]> create personal
New vault password: ••••••••
Confirm password: ••••••••
Vault 'personal' created.
vault[personal|0]> gen 16
Generated (≈98 bits): K9#mP2$qLv8nW!j&
vault[personal|0]> add github myuser
Service password: ••••••••
Notes (optional): 2FA on my phone
Entry 'github' added.
vault[personal*|1]> commit
Changes committed.
vault[personal|1]> copy github
Password for 'github' copied to clipboard (cleared in 30 s).
vault[personal|1]> lock
Vault locked.
vault[locked]>
Arguments with spaces go in quotes, as in a shell: add "My Bank" "joao silva", get "My Bank", export "~/My Files/vault.csv". Tab writes the quotes for you. Single quotes work too, and outside them \ keeps the next character as it is (My\ Bank).
| Command | Arguments | Description |
|---|---|---|
| Core | ||
create |
<name> |
Create a new vault (asks for the password twice; at least 8 characters) |
unlock |
<name> |
Unlock an existing vault |
lock |
— | Lock the current vault (prompts to commit if there are unsaved changes) |
passwd |
— | Change the current vault's master password (at least 8 characters) |
drop |
<name> |
Permanently delete a vault and its backup (vault must be locked) |
| Entries | ||
add |
<service> <user> |
Add a credential entry (asks for its password and optional notes) |
get |
<service> |
Show a credential entry (username, password and notes) |
find |
[term] |
Pick an entry from an interactive list (↑/↓ to move, type to filter), then copy, show, update or remove it, or see its history |
history |
<service> |
Show an entry's previous passwords, newest first, with the date each was replaced (the last 10 are kept) |
copy |
<service> [user] |
Copy a password (or, with user, the username) to the clipboard, out of clipboard managers' history; cleared after 30 s or when vault exits |
update |
<service> |
Update an entry: blank keeps the username or password; a new password sends the old one to the history; the notes come pre-filled to edit |
mv |
<service> <new> |
Rename an entry (password, notes and history stay) |
rm |
<service> |
Remove a credential entry |
gen |
[length] [nosymbols] [noambiguous] |
Generate a random password (8–256 chars, default 20), without symbols or look-alike characters (Il1O0o) if asked; shows its strength in bits |
gen words |
[count] |
Generate a passphrase of 4–20 words (default 6, ≈78 bits), such as maple-orbit-cactus-violin-ember-tundra |
| Data & I/O | ||
commit |
— | Save changes to disk |
status |
— | Show what changed since the last commit (added, removed, renamed, changed fields) |
discard |
— | Drop the uncommitted changes, keeping the vault open (asks first) |
health |
— | Report reused passwords and passwords under 12 characters, and list the 5 oldest by when they were set. Never shows a password |
restore |
— | Load the backup (the version the last commit replaced) as uncommitted changes: status to review, commit to keep, discard to undo |
rename |
<new_name> |
Rename the current vault file |
ls/list |
— | List vaults (when locked) or entries (when unlocked) |
info |
— | Show the vault name, entry count and key strength |
export |
<path> |
Export entries to a CSV file (Warning: Plaintext!). Created 0600; never overwrites an existing file |
import |
<path> |
Import entries from a CSV file (merges new services; all or nothing, see below) |
| CLI | ||
clear |
— | Clear the screen and the scrollback (hides passwords shown by get) |
help |
— | Print help |
exit |
— | Exit (prompts to commit if there are unsaved changes) |
vault[locked]> # vault is locked
vault[myname|3]> # unlocked, 3 entries
vault[myname*|3]> # unlocked, unsaved changes
An unlocked vault left at the prompt for 5 minutes without a command locks itself, wiping the key and the entries from memory. If it has uncommitted changes, it stays open instead and says so once: locking would throw them away, so committing or discarding them is up to you.
Set the time with VAULT_AUTO_LOCK, in seconds (0 turns it off):
VAULT_AUTO_LOCK=120 vault # lock after 2 minutesThe time only runs at the main prompt: in the middle of a command (a password prompt, the find list) nothing locks.
All vault files are stored in ~/.vault/. On Unix the directory is 0700 and every file 0600; if an older version left them more open, they are tightened on the next commit:
~/.vault/
├── .lock # taken during each commit, so sessions never write at once
├── personal.vault # encrypted vault
├── personal.bkp # previous version, kept by each commit (`restore` loads it)
└── work.vault
Each .vault file (format v3) is a small header followed by the encrypted entries (service, username, password, notes, previous passwords and timestamps):
VAULT\0and the format version;- the Argon2id cost used for this vault's key (memory, passes, lanes);
salt: 16 random bytes, new at creation and at each password change;nonce: 12 random bytes, new at every commit;- the AES-256-GCM ciphertext of the entries. The header is authenticated with it, so changing any byte makes the vault refuse to open.
Vaults written by older versions (v0 to v2) still open. On unlock they are rewritten in the current format, with a stronger key if theirs was cheaper, and the previous file stays as the .bkp until the next commit. The byte-level layout is in docs/SECURITY.md.
The export and import commands use standard RFC 4180 CSV files with a header row:
service,username,password,notes
github,myuser,mysecret123,
aws,admin,"super,secure!",root accountimportfinds the columns by name, in any order and any case:service,usernameandpasswordare required, andnotesis optional (exports from older versions don't have it). Other columns are ignored.- Every row has as many fields as the header. A field with a comma or a quote must be quoted, as in the
awsrow above. - Service names can have spaces (type them in quotes), but can't be empty or contain control characters. Spaces around a name are trimmed.
- Import is all or nothing: if a row is invalid, the error names its line and no entry is added.
- Importing merges entries: if a service already exists in the vault, or appears twice in the file, the first version is kept and the other row is skipped.
- The password history is not exported.
- Don't round-trip an export through a spreadsheet. Opening the CSV in Excel, LibreOffice or Google Sheets and saving it again turns values like
0042into42, long numbers into1.2E+15and some text into dates. Edit it in a text editor, or import it into the spreadsheet as text.
MIT.
Passphrases use the EFF large wordlist (src/domain/eff_large_wordlist.txt, unchanged), by the Electronic Frontier Foundation, under CC BY 3.0 US.
- Architecture — module design, data flow, hexagonal pattern
- Security Model — cryptographic choices, threat model, limitations
- Testing — test layout, how to run it, conventions