# zkv Β· Zero Knowledge Vault
> π A local-first, end-to-end encrypted personal vault. Your passphrase never leaves your machine, keys never touch disk, and a `.zkv` file away from your computer is just meaningless ciphertext.





A terminal-based manager for passwords / notes / cards with a sci-fi TUI ([ratatui-sci-fi](https://crates.io/crates/ratatui-sci-fi) Cyberpunk theme). All data is encrypted at rest with **Argon2id + XChaCha20-Poly1305**; ships with a fully **scriptable, TTY-free headless CLI**.
---
## β¨ Features
- π **Zero-knowledge encryption** β Argon2id derives the key from your passphrase; XChaCha20-Poly1305 encrypts the whole database. Keys are zeroized on drop and never written to disk.
- ποΈ **Multiple vaults** β Each `.zkv` file has its own passphrase; manage several vaults side by side.
- π **Multiple entry types** β Password, Note, and Card presets; fields are stored as JSON for easy extension.
- π **Full-text search** β Powered by SQLite FTS5 over titles and content.
- π·οΈ **Categories & tags** β Hierarchical categories + many-to-many tags + favorites, freely combinable.
- πΌοΈ **Embedded attachments** β Images / documents are stored inside the database and encrypted with it.
- π’ **TOTP codes** β Store 2FA secrets and generate live 6-digit codes (RFC 6238). Import a QR directly: `--qr <local-image>` or `--qr-url <http(s)/data: URL>` auto-decodes the `otpauth://` URI into the entry (no need to scan/extract the text yourself).
- π§© **Field templates** β Generic field/template model with 8 built-in presets (password/note/card/wifi/bank/ssh/identity/email); fields are typed (Text/Secret/Multiline/TOTP) and drive rendering/copying; old vaults auto-migrate.
- π² **Password generation** β CSPRNG strong passwords (configurable length / symbols / ambiguous chars).
- π» **Headless CLI** β Fully scriptable, no TTY required; passphrase from env var / file / prompt.
- π **Passphrase-caching agent** β Transparently caches the derived key: type the passphrase once and skip Argon2id across consecutive commands; auto-clears on idle (ssh-agent/sudo style; key **lives in RAM only, never on disk**).
- π **Import / export** β Lossless JSON round-trip, or flat CSV (passwords), for migration and backup.
- π¨ **Sci-fi TUI** β header status bar + list/detail panes + footer keybar, neon rounded panels, fully keyboard-driven.
- β±οΈ **Security details** β Clipboard auto-clears 20s after copying; idle auto-lock; atomic writes prevent corruption; files are `0600`.
## π₯οΈ Preview
```
β Items ββββββββββββββ¬β Detail βββββββββββββββββββ
β β
[PW] GitHub Loginβ Title: GitHub Login β
β [PW] GitLab Tokenβ Type: Password β
β β
[NO] Secret Diaryβ Username: alice β
β [CD] Visa **** β Password: β’β’β’β’β’β’β’β’β’ [y] β
β β URL: github.com β
β β TOTP: 586148 (~14s) β
β β π report.pdf (12345) β
ββββββββββββββββββββββ΄ββββββββββββββββββββββββββββ
[normal] n:new e:edit x:del /:search y:copy o:otp a:att l:lock c:cat t:tag q:quit
```
## π Quick Start
Requires Rust 1.85+ (edition 2024).
```bash
git clone <repo-url> zkv && cd zkv
cargo run --release -- new ~/my.zkv # create a new vault (set passphrase in TUI)
cargo run --release -- open ~/my.zkv # open an existing vault
```
> π‘ `<path>` is optional β it defaults to `~/.zkv/default.zkv`. e.g. `zkv init`, `zkv ls`, `zkv open`, `zkv get 1`.
Or install to `$CARGO_HOME/bin`:
```bash
cargo install --path .
zkv new ~/my.zkv
```
## π» Headless CLI
Parallel to the TUI, fully **scriptable and TTY-free** (passphrase from `ZKV_PASSPHRASE` env / `--passfile` / interactive prompt):
```bash
zkv init [~/my.zkv] # non-interactive create (refuses to overwrite); omit path β ~/.zkv/default.zkv
zkv passwd [~/my.zkv] # change the master passphrase (verify old; new salt + key)
zkv gen [24] [--no-symbols] [--no-ambiguous] # strong random password (no vault needed)
# <path> is optional (defaults to ~/.zkv/default.zkv); in multi-positional commands path is always last:
zkv ls [~/my.zkv] [-t password] [--tag T] [--cat C] [-q github] [-F|--favorite] [--json]
zkv get <id> [~/my.zkv] [-f password] # -f prints a raw field; or --find <title>
zkv search <query> [~/my.zkv]
zkv otp <id> [~/my.zkv] # print the current TOTP 6-digit code
zkv cp <id> [~/my.zkv] [-f otp] [--clear 20]
zkv attach add <id> <file> [~/my.zkv] [--mime M] Β· zkv attach ls <id> [~/my.zkv]
zkv attach get <id> <att> [~/my.zkv] [-o file|>file] Β· zkv attach rm <id> <att> [~/my.zkv]
# Import / export (lossless JSON, includes attachments; CSV is passwords-only):
zkv agent status Β· zkv agent stop Β· zkv lock # status / stop / clear cached keys
```
Examples: `ZKV_PASSPHRASE=secret zkv ls vault.zkv --type password --json` Β· `zkv otp vault.zkv 3` Β· `code=$(zkv gen 24)`.
### π Shell completions (bash / zsh / fish / elvish / powershell)
`zkv completions <shell>` prints the completion script to stdout β source it or drop it in your completions directory:
```bash
# bash (apply now + persist in ~/.bashrc)
eval "$(zkv completions bash)"
echo 'eval "$(zkv completions bash)"' >> ~/.bashrc
# or install system-wide (requires sudo):
# other shells work the same: zkv completions zsh / fish / elvish / powershell
```
Completions cover every subcommand, field names (`-f password|otp|totp|...`), and flags like `--qr` / `--qr-url`.
## π Passphrase-caching agent
Every command otherwise reads the passphrase and runs a full Argon2id derivation (64MiB/3/4, ~hundreds of ms). The agent is a **transparent** background process that caches the derived master key **in memory only**, so consecutive commands ask for the passphrase just once and skip the KDF:
```bash
zkv ls ~/my.zkv # first time: type passphrase β agent auto-spawns in the background, caches the key
zkv otp 1 ~/my.zkv # afterwards (within 5 min by default): no passphrase prompt, near-instant
zkv cp 2 ~/my.zkv
```
- **Automatic**: self-spawns the first time a passphrase is needed; clears its key and exits after `ZKV_LOCK_SECS` (default 300s β the same variable as the TUI auto-lock) of idleness. Changing the passphrase (`passwd`) invalidates the cache.
- **Secure**: the derived key lives **only in the agent process's RAM** (`Zeroizing`, cleared on drop) and is **never written to disk**; it reaches same-uid clients over a 0600 local Unix socket (same model as ssh-agent); access is gated by a 0700 private dir (`$XDG_RUNTIME_DIR` preferred). Any failure (unreachable / version mismatch / vault re-encrypted elsewhere) **silently falls back** to the normal passphrase prompt β never hangs or corrupts.
- **Control / disable**:
```bash
zkv agent status zkv agent stop zkv lock zkv ls --no-agent ... ZKV_LOCK_SECS=0 ```
> The agent is active on **Unix** only (Linux/macOS/WSL); elsewhere it is a no-op and every command prompts as usual.
## β¨οΈ TUI Keybindings
| `n` | New entry (password / note / card) |
| `e` | Edit current entry |
| `x` | Delete current entry (confirm required) |
| `/` | Search |
| `j` / `k`, `β` / `β` | Move up / down |
| `y` | Copy password to clipboard (auto-clears after 20s) |
| `o` | Copy the current TOTP code |
| `a` | Attachment manager (add / export / delete) |
| `l` | Lock now (clears keys and data from memory) |
| `c` / `t` | Category / tag manager (add / rename / delete) |
| `Tab` / `β` / `β` | Cycle fields while editing |
| `Enter` | Save / confirm / submit passphrase |
| `Esc` | Cancel / back |
| `q` | Quit |
> **Auto-lock**: the TUI locks itself after `ZKV_LOCK_SECS` (default 300s, `0` disables) of inactivity; re-enter the passphrase in place to resume. The headless CLI's passphrase-caching agent reuses this same variable as its idle TTL (see "Passphrase-caching agent" above).
## π‘οΈ Security
**Cryptographic scheme**
| Key derivation (KDF) | Argon2id | m=64MiB, t=3, p=4, salt=16B, output 32B |
| Symmetric encryption | XChaCha20-Poly1305 | key=32B, nonce=24B (fresh each save), tag=16B (AEAD) |
| TOTP | RFC 6238 | HMAC-SHA1, 30s, 6 digits, base32 secret |
**Granularity**: the entire SQLite database is encrypted as a single blob. On unlock it is decrypted into **memory** (`:memory:`); on exit/lock it is zeroized. On save it is re-encrypted with the cached derived key (a fresh nonce each time, **no Argon2 re-run**) and written back atomically. Plaintext is never persisted.
**Threat model**
- β
Defends against: offline theft of a `.zkv` file (only brute-forceable, made costly by Argon2id); plaintext on disk; temp files are `0600` with CSPRNG names; metadata leakage (entry counts, tag names β all encrypted); clipboard auto-clear; idle auto-lock; the agent's cached key lives in RAM only, never on disk, and transits a 0600 local socket to same-uid clients.
- β οΈ Does **not** defend against: a fully compromised host (keyloggers, memory dumps, cold-boot attacks).
- β οΈ **A forgotten passphrase means unrecoverable data** β the price of zero knowledge. Back up your passphrase and `.zkv` file carefully.
## π§± Tech Stack
- **Language**: Rust (edition 2024)
- **TUI**: [ratatui](https://crates.io/crates/ratatui) Β· [crossterm](https://crates.io/crates/crossterm) Β· [ratatui-sci-fi](https://crates.io/crates/ratatui-sci-fi)
- **Database**: [rusqlite](https://crates.io/crates/rusqlite) (bundled SQLite, with FTS5)
- **Crypto**: [argon2](https://crates.io/crates/argon2) Β· [chacha20poly1305](https://crates.io/crates/chacha20poly1305) Β· [zeroize](https://crates.io/crates/zeroize) Β· [secrecy](https://crates.io/crates/secrecy)
- **TOTP**: [hmac](https://crates.io/crates/hmac) Β· [sha1](https://crates.io/crates/sha1) Β· [data-encoding](https://crates.io/crates/data-encoding)
- **QR import**: [image](https://crates.io/crates/image) Β· [rqrr](https://crates.io/crates/rqrr) Β· [ureq](https://crates.io/crates/ureq) (decode a local image / fetch a remote one; rustls)
- **Other**: [clap](https://crates.io/crates/clap), [serde](https://crates.io/crates/serde), [thiserror](https://crates.io/crates/thiserror), [color-eyre](https://crates.io/crates/color-eyre), [rpassword](https://crates.io/crates/rpassword), [getrandom](https://crates.io/crates/getrandom)
## ποΈ Architecture
Layered design with one-way dependencies (lower layers never reference upper ones), following MVC (`App` = Model + Controller, UI = View):
```
error(L0) β crypto/model/totp(L1) β db/vault(L2) β store/search/clipboard(L3) β app(L4) β ui(L5) β main(L6)
β cli (headless frontend, parallel to ui) Β· agent (Unix daemon caching the derived key)
```
See [docs/PROGRESS.md](docs/PROGRESS.md) and [docs/prd/zkv.md](docs/prd/zkv.md).
## π `.zkv` File Format
Little-endian; a 58-byte fixed header followed by ciphertext:
```
[4 "ZKV1"][1 ver][1 flags][4 m_kib][4 t_cost][4 p_cost][16 salt][24 nonce][N ciphertext]
```
KDF parameters are stored in the file, so they can be tuned in the future while old files remain readable; a failed Poly1305 check is interpreted as a wrong passphrase or a corrupted file.
## π οΈ Development
```bash
cargo test # unit / integration tests (215 passed)
cargo clippy --all-targets # 0 warnings
just e2e # PTY end-to-end (drives the real binary, 7 cases)
cargo build --release # release build
```
## πΊοΈ Roadmap
- [x] Category / tag add-rename-delete (CLI + TUI)
- [x] Import / export (JSON / CSV)
- [x] TOTP codes + headless CLI + idle auto-lock
- [x] Field templates (8 built-in presets + generic field model; custom-template CRUD is a follow-up)
- [x] Passphrase-caching agent (transparently cache the derived key, skip Argon2id; ssh-agent/sudo style)
- [ ] KeePass import / export
- [ ] Per-page encryption for very large vaults (on demand: ~50β200ms per save under 100MB today; see [PROGRESS.md](docs/PROGRESS.md) 2026-06-21 decision)
- [x] Windows clipboard backend (PowerShell `Set-Clipboard`, via stdin, UTF-8)
## π License
MIT