|
| 1 | +# kpcache |
| 2 | + |
| 3 | +Tiny shell wrapper that caches your **KeePassXC master password in tmpfs** |
| 4 | +so that `keepassxc-cli` lookups don't prompt for it on every call. |
| 5 | + |
| 6 | +For headless / WSL / server setups where you want to use KeePassXC entries |
| 7 | +from CLI tools (himalaya, mbsync, mutt, isync, restic, …) without typing |
| 8 | +the master password each time and without running a long-lived daemon. |
| 9 | + |
| 10 | +## Quickstart |
| 11 | + |
| 12 | +```sh |
| 13 | +git clone https://github.com/magnattic/kpcache ~/.local/share/kpcache |
| 14 | +ln -s ~/.local/share/kpcache/bin/kp{unlock,get,lock} ~/.local/bin/ |
| 15 | + |
| 16 | +export KP_DB=~/Passwords.kdbx |
| 17 | +kpunlock # type master password once |
| 18 | +kpget "Email/personal" # → password, no prompt |
| 19 | +kpget "Email/personal" -a User # any keepassxc-cli show option |
| 20 | +kplock # clear the cache |
| 21 | +``` |
| 22 | + |
| 23 | +## Why |
| 24 | + |
| 25 | +`keepassxc-cli` is fast - it's the official C++ binary that uses native |
| 26 | +Argon2/AES (via Botan) - but it prompts for the master password on every |
| 27 | +invocation. That's unworkable when an MTA polls IMAP every 60 seconds or |
| 28 | +when scripts make many lookups. |
| 29 | + |
| 30 | +Existing solutions don't fit headless setups well: |
| 31 | + |
| 32 | +- **kpsh** - Python daemon, but uses pure-Python Argon2/AES. With modern |
| 33 | + KeePassXC default KDF settings, the initial unlock can take *minutes* |
| 34 | + in pure Python (vs. seconds with the official binary). |
| 35 | +- **git-credential-keepassxc** - needs the KeePassXC GUI running and |
| 36 | + unlocked, with the browser-extension protocol. Doesn't work headless. |
| 37 | +- **keepasxcli-wrapper** - unmaintained since 2021. |
| 38 | + |
| 39 | +`kpcache` is ~80 lines of bash that: |
| 40 | + |
| 41 | +1. Prompts for the master password once. |
| 42 | +2. Stores it in `/dev/shm/<uid>-kpcache` with mode `0600`. |
| 43 | +3. Each subsequent `kpget` reads from the cache and shells out to |
| 44 | + `keepassxc-cli show` - fast because it's the official binary. |
| 45 | + |
| 46 | +The master password lives in RAM only (tmpfs is in-memory). It's gone |
| 47 | +after reboot or `kplock`. |
| 48 | + |
| 49 | +## Install |
| 50 | + |
| 51 | +### Dependencies |
| 52 | + |
| 53 | +You need `keepassxc-cli` in `$PATH`. |
| 54 | + |
| 55 | +```sh |
| 56 | +# Debian/Ubuntu (newer releases also have keepassxc-minimal - no GUI deps) |
| 57 | +sudo apt install keepassxc |
| 58 | + |
| 59 | +# Arch |
| 60 | +sudo pacman -S keepassxc |
| 61 | + |
| 62 | +# Fedora |
| 63 | +sudo dnf install keepassxc |
| 64 | + |
| 65 | +# macOS - brew installs CLI inside the .app bundle, symlink it into PATH: |
| 66 | +brew install --cask keepassxc |
| 67 | +sudo ln -s /Applications/KeePassXC.app/Contents/MacOS/keepassxc-cli \ |
| 68 | + /usr/local/bin/keepassxc-cli |
| 69 | +``` |
| 70 | + |
| 71 | +### kpcache itself |
| 72 | + |
| 73 | +```sh |
| 74 | +git clone https://github.com/magnattic/kpcache ~/.local/share/kpcache |
| 75 | +ln -s ~/.local/share/kpcache/bin/kp{unlock,get,lock} ~/.local/bin/ |
| 76 | +``` |
| 77 | + |
| 78 | +Make sure `~/.local/bin` is in your `$PATH`. |
| 79 | + |
| 80 | +## Configure |
| 81 | + |
| 82 | +Set the database path either as an env var or in |
| 83 | +`~/.config/kpcache/config`: |
| 84 | + |
| 85 | +```sh |
| 86 | +mkdir -p ~/.config/kpcache |
| 87 | +cat > ~/.config/kpcache/config <<'EOF' |
| 88 | +KPCACHE_DB="/path/to/your.kdbx" |
| 89 | +# KPCACHE_KEYFILE="/path/to/keyfile" # if your DB uses one |
| 90 | +# KPCACHE_TTL=28800 # optional: expire cache after 8h |
| 91 | +EOF |
| 92 | +``` |
| 93 | + |
| 94 | +## Use |
| 95 | + |
| 96 | +```sh |
| 97 | +kpunlock # prompts for master password, caches it |
| 98 | +kpget "Email/personal" # prints the password |
| 99 | +kpget "Email/personal" -a Username # prints the username |
| 100 | +kpget "Servers/prod" -a URL # any keepassxc-cli show -a value |
| 101 | +kpget "Email/personal" -s # show all attributes |
| 102 | +kplock # clears the cache (manual lock) |
| 103 | +``` |
| 104 | + |
| 105 | +## Integrate with other tools |
| 106 | + |
| 107 | +### himalaya (CLI mail client) |
| 108 | + |
| 109 | +```toml |
| 110 | +# ~/.config/himalaya/config.toml - tested with himalaya v1.2+ |
| 111 | +backend.auth.cmd = "kpget Email/personal" |
| 112 | +``` |
| 113 | + |
| 114 | +### mbsync / isync |
| 115 | + |
| 116 | +``` |
| 117 | +# ~/.mbsyncrc |
| 118 | +PassCmd "+kpget 'Email/personal'" |
| 119 | +``` |
| 120 | + |
| 121 | +The `+` prefix tells mbsync to suppress logging the command. |
| 122 | + |
| 123 | +### mutt |
| 124 | + |
| 125 | +``` |
| 126 | +set imap_pass = "`kpget Email/personal`" |
| 127 | +``` |
| 128 | + |
| 129 | +### restic |
| 130 | + |
| 131 | +```sh |
| 132 | +export RESTIC_PASSWORD_COMMAND="kpget Backup/restic-repo" |
| 133 | +restic snapshots |
| 134 | +``` |
| 135 | + |
| 136 | +See [`examples/`](examples/) for full configs. |
| 137 | + |
| 138 | +## Security model |
| 139 | + |
| 140 | +**What kpcache protects against:** |
| 141 | + |
| 142 | +- The master password is never passed as a command-line argument, so it |
| 143 | + doesn't appear in `ps aux` or shell history. |
| 144 | +- The cache file is created atomically with `install -m 600 /dev/null`, |
| 145 | + so there's no readable window even before `chmod`. |
| 146 | +- `set -euo pipefail` plus an `ERR/INT/TERM` trap ensure the cache is |
| 147 | + removed if `kpunlock` is interrupted mid-write. |
| 148 | +- Entry paths are passed to `keepassxc-cli` after `--`, so an entry name |
| 149 | + starting with `-` cannot be misinterpreted as an option. |
| 150 | + |
| 151 | +**What kpcache does *not* protect against:** |
| 152 | + |
| 153 | +- **Root.** A root user can read any process memory or any file on the |
| 154 | + system. Not a goal here. |
| 155 | +- **Memory forensics.** The decrypted master password sits in tmpfs RAM. |
| 156 | + An attacker with kernel access (or a coredump) can read it. |
| 157 | +- **A compromised user account.** Any other process running as your user |
| 158 | + can read `/dev/shm/<uid>-kpcache`. The model assumes your local user |
| 159 | + is trusted. |
| 160 | +- **Hardware tokens (YubiKey challenge-response).** Not currently |
| 161 | + supported. Patches welcome. |
| 162 | + |
| 163 | +If your threat model requires per-process secret isolation, hardware |
| 164 | +tokens, or memory hardening, use a kernel-keyring solution or the |
| 165 | +KeePassXC Secret Service integration with a running GUI session. |
| 166 | + |
| 167 | +## Why not just lower the KDF rounds? |
| 168 | + |
| 169 | +You could lower the Argon2 (or AES-KDF) parameters in the database |
| 170 | +settings to make pykeepass-based daemons (kpsh, passhole) usable. But |
| 171 | +that weakens the KDF's brute-force resistance against an attacker who |
| 172 | +has stolen the `.kdbx` file. Sticking with the official KeePassXC binary |
| 173 | +(which has native C Argon2 via Botan) means you keep the strong KDF and |
| 174 | +still get fast lookups. |
| 175 | + |
| 176 | +## Alternatives |
| 177 | + |
| 178 | +- [kpsh](https://git.goral.net.pl/keepass-shell.git) - daemon, pure-Python |
| 179 | + pykeepass. Good if your DB uses moderate KDF rounds. |
| 180 | +- [git-credential-keepassxc](https://github.com/Frederick888/git-credential-keepassxc) |
| 181 | + - talks to a running KeePassXC GUI via the browser-extension protocol. |
| 182 | + Best if you have a GUI session anyway. |
| 183 | +- [pass](https://www.passwordstore.org/) - drop KeePass, use GPG-encrypted |
| 184 | + files with `gpg-agent` for caching. Larger migration. |
| 185 | + |
| 186 | +## License |
| 187 | + |
| 188 | +MIT - see [LICENSE](LICENSE). |
0 commit comments