Skip to content

Commit 1a11bb8

Browse files
committed
Rename to kpxc, add generic wrapper and integration tests
Project renamed kpcache -> kpxc. The kpxc command is a thin wrapper around any keepassxc-cli subcommand using the cached master password (ls, search, db-info, attachment-export, ...). kpget stays as the short hot-path for `show -a Password` in script configs. Breaking changes from v0.1.0: - Env vars renamed KPCACHE_* -> KP_* (KP_DB, KP_KEYFILE, KP_TTL, KP_CACHE, KP_CONFIG). Avoids collision with keepassxc-cli's own KPXC_CONFIG / KPXC_CONFIG_LOCAL env vars. - Cache file moved /dev/shm/<uid>-kpcache -> /dev/shm/<uid>-kpxc - Config dir moved ~/.config/kpcache/ -> ~/.config/kpxc/ New: - bin/kpxc generic wrapper (subcommands streamed to keepassxc-cli with cached password injected, denylist for no-DB subcommands) - tests/test.sh integration suite (15 tests, builds ephemeral DB with low KDF rounds, exercises kpget/kpxc/kplock end-to-end) - CI runs both shellcheck and the test suite - README adds disclaimer that this is an unofficial third-party wrapper not affiliated with the KeePassXC project
1 parent 0a121f2 commit 1a11bb8

8 files changed

Lines changed: 354 additions & 87 deletions

File tree

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
shellcheck:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: ludeeus/action-shellcheck@master
14+
with:
15+
scandir: ./bin
16+
severity: warning
17+
18+
test:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
- name: Install keepassxc-cli
23+
run: |
24+
sudo apt-get update
25+
sudo apt-get install -y keepassxc
26+
- name: Run integration tests
27+
run: bash tests/test.sh

.github/workflows/shellcheck.yml

Lines changed: 0 additions & 16 deletions
This file was deleted.

README.md

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
1-
# kpcache
1+
# kpxc
22

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.
3+
Cached wrapper around `keepassxc-cli`: unlock once per session, then run any
4+
KeePassXC subcommand without reprompting for the master password.
55

66
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.
7+
from CLI tools (himalaya, mbsync, mutt, isync, restic) without typing the
8+
master password each time and without running a long-lived daemon.
9+
10+
> Unofficial third-party wrapper. Not affiliated with the KeePassXC project.
911
1012
## Quickstart
1113

1214
```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+
git clone https://github.com/magnattic/kpxc ~/.local/share/kpxc
16+
ln -s ~/.local/share/kpxc/bin/kp{unlock,get,lock,xc} ~/.local/bin/
1517

1618
export KP_DB=~/Passwords.kdbx
1719
kpunlock # type master password once
18-
kpget "Email/personal" # password, no prompt
20+
kpget "Email/personal" # password, no prompt
1921
kpget "Email/personal" -a User # any keepassxc-cli show option
22+
kpxc ls /Email # any keepassxc-cli subcommand
2023
kplock # clear the cache
2124
```
2225

2326
## Why
2427

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
28+
`keepassxc-cli` is fast (it's the official C++ binary using native
29+
Argon2/AES via Botan), but it prompts for the master password on every
2730
invocation. That's unworkable when an MTA polls IMAP every 60 seconds or
2831
when scripts make many lookups.
2932

@@ -36,12 +39,12 @@ Existing solutions don't fit headless setups well:
3639
unlocked, with the browser-extension protocol. Doesn't work headless.
3740
- **keepasxcli-wrapper** - unmaintained since 2021.
3841

39-
`kpcache` is ~80 lines of bash that:
42+
`kpxc` is ~120 lines of bash that:
4043

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.
44+
1. Prompts for the master password once (`kpunlock`).
45+
2. Stores it in `/dev/shm/<uid>-kpxc` with mode `0600`.
46+
3. Each subsequent `kpget` / `kpxc` reads from the cache and shells out
47+
to `keepassxc-cli`. Fast because it's the official binary.
4548

4649
The master password lives in RAM only (tmpfs is in-memory). It's gone
4750
after reboot or `kplock`.
@@ -53,7 +56,7 @@ after reboot or `kplock`.
5356
You need `keepassxc-cli` in `$PATH`.
5457

5558
```sh
56-
# Debian/Ubuntu (newer releases also have keepassxc-minimal - no GUI deps)
59+
# Debian/Ubuntu (newer releases also have keepassxc-minimal, no GUI deps)
5760
sudo apt install keepassxc
5861

5962
# Arch
@@ -62,52 +65,67 @@ sudo pacman -S keepassxc
6265
# Fedora
6366
sudo dnf install keepassxc
6467

65-
# macOS - brew installs CLI inside the .app bundle, symlink it into PATH:
68+
# macOS: brew installs the CLI inside the .app bundle, symlink it into PATH:
6669
brew install --cask keepassxc
6770
sudo ln -s /Applications/KeePassXC.app/Contents/MacOS/keepassxc-cli \
6871
/usr/local/bin/keepassxc-cli
6972
```
7073

71-
### kpcache itself
74+
### kpxc itself
7275

7376
```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/
77+
git clone https://github.com/magnattic/kpxc ~/.local/share/kpxc
78+
ln -s ~/.local/share/kpxc/bin/kp{unlock,get,lock,xc} ~/.local/bin/
7679
```
7780

7881
Make sure `~/.local/bin` is in your `$PATH`.
7982

8083
## Configure
8184

8285
Set the database path either as an env var or in
83-
`~/.config/kpcache/config`:
86+
`~/.config/kpxc/config`:
8487

8588
```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
89+
mkdir -p ~/.config/kpxc
90+
cat > ~/.config/kpxc/config <<'EOF'
91+
KPXC_DB="/path/to/your.kdbx"
92+
# KPXC_KEYFILE="/path/to/keyfile" # if your DB uses one
93+
# KPXC_TTL=28800 # optional: expire cache after 8h
9194
EOF
9295
```
9396

9497
## Use
9598

99+
Two commands cover almost everything:
100+
96101
```sh
97102
kpunlock # prompts for master password, caches it
98-
kpget "Email/personal" # prints the password
103+
kpget "Email/personal" # prints the password (hot path for scripts)
99104
kpget "Email/personal" -a Username # prints the username
100105
kpget "Servers/prod" -a URL # any keepassxc-cli show -a value
101106
kpget "Email/personal" -s # show all attributes
102107
kplock # clears the cache (manual lock)
103108
```
104109

110+
For everything beyond `show`, use the generic wrapper:
111+
112+
```sh
113+
kpxc ls /Email # list entries in a group
114+
kpxc search github # full-text search
115+
kpxc db-info # database metadata
116+
kpxc show -a Password Servers/prod # equivalent to kpget Servers/prod
117+
```
118+
119+
`kpxc` forwards any `keepassxc-cli` subcommand and injects the cached
120+
password automatically. `kpget` is just a shortcut for the most common
121+
case (printing one attribute) and stays terse in config files.
122+
105123
## Integrate with other tools
106124

107125
### himalaya (CLI mail client)
108126

109127
```toml
110-
# ~/.config/himalaya/config.toml - tested with himalaya v1.2+
128+
# ~/.config/himalaya/config.toml, tested with himalaya v1.2+
111129
backend.auth.cmd = "kpget Email/personal"
112130
```
113131

@@ -137,28 +155,28 @@ See [`examples/`](examples/) for full configs.
137155

138156
## Security model
139157

140-
**What kpcache protects against:**
158+
**What kpxc protects against:**
141159

142160
- The master password is never passed as a command-line argument, so it
143161
doesn't appear in `ps aux` or shell history.
144162
- The cache file is created atomically with `install -m 600 /dev/null`,
145163
so there's no readable window even before `chmod`.
146164
- `set -euo pipefail` plus an `ERR/INT/TERM` trap ensure the cache is
147165
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.
166+
- Entry paths are passed to `keepassxc-cli show` after `--`, so an entry
167+
name starting with `-` cannot be misinterpreted as an option.
150168

151-
**What kpcache does *not* protect against:**
169+
**What kpxc does *not* protect against:**
152170

153171
- **Root.** A root user can read any process memory or any file on the
154172
system. Not a goal here.
155173
- **Memory forensics.** The decrypted master password sits in tmpfs RAM.
156174
An attacker with kernel access (or a coredump) can read it.
157175
- **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.
176+
can read `/dev/shm/<uid>-kpxc`. The model assumes your local user is
177+
trusted.
160178
- **Hardware tokens (YubiKey challenge-response).** Not currently
161-
supported. Patches welcome.
179+
supported. PRs welcome.
162180

163181
If your threat model requires per-process secret isolation, hardware
164182
tokens, or memory hardening, use a kernel-keyring solution or the
@@ -175,14 +193,14 @@ still get fast lookups.
175193

176194
## Alternatives
177195

178-
- [kpsh](https://git.goral.net.pl/keepass-shell.git) - daemon, pure-Python
196+
- [kpsh](https://git.goral.net.pl/keepass-shell.git): daemon, pure-Python
179197
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.
198+
- [git-credential-keepassxc](https://github.com/Frederick888/git-credential-keepassxc):
199+
talks to a running KeePassXC GUI via the browser-extension protocol.
182200
Best if you have a GUI session anyway.
183-
- [pass](https://www.passwordstore.org/) - drop KeePass, use GPG-encrypted
201+
- [pass](https://www.passwordstore.org/): drop KeePass, use GPG-encrypted
184202
files with `gpg-agent` for caching. Larger migration.
185203

186204
## License
187205

188-
MIT - see [LICENSE](LICENSE).
206+
MIT, see [LICENSE](LICENSE).

bin/kpget

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@
99

1010
set -euo pipefail
1111

12-
CACHE="${KPCACHE_FILE:-/dev/shm/$(id -u)-kpcache}"
13-
CONFIG="${KPCACHE_CONFIG:-$HOME/.config/kpcache/config}"
12+
CACHE="${KP_CACHE:-/dev/shm/$(id -u)-kpxc}"
13+
CONFIG="${KP_CONFIG:-$HOME/.config/kpxc/config}"
1414

1515
if [[ -f "$CONFIG" ]]; then
1616
# shellcheck source=/dev/null
1717
source "$CONFIG"
1818
fi
1919

20-
DB="${KP_DB:-${KPCACHE_DB:-}}"
21-
KEYFILE="${KP_KEYFILE:-${KPCACHE_KEYFILE:-}}"
20+
DB="${KP_DB:-}"
21+
KEYFILE="${KP_KEYFILE:-}"
2222

2323
if [[ -z "$DB" ]]; then
24-
echo "No database configured (set \$KP_DB or KPCACHE_DB in config)." >&2
24+
echo "No database configured (set \$KP_DB env var or KP_DB= in config)." >&2
2525
exit 1
2626
fi
2727

@@ -31,22 +31,22 @@ if [[ $# -lt 1 ]]; then
3131
fi
3232

3333
if [[ ! -f "$CACHE" ]]; then
34-
echo "No cached master password - run 'kpunlock' first." >&2
34+
echo "No cached master password - run 'kpunlock' first." >&2
3535
exit 1
3636
fi
3737

38-
# Optional TTL check (portable mtime - GNU stat -c, BSD/macOS stat -f)
39-
if [[ -n "${KPCACHE_TTL:-}" ]]; then
38+
# Optional TTL check (portable mtime: GNU stat -c, BSD/macOS stat -f).
39+
if [[ -n "${KP_TTL:-}" ]]; then
4040
if mtime=$(stat -c %Y "$CACHE" 2>/dev/null); then :
4141
elif mtime=$(stat -f %m "$CACHE" 2>/dev/null); then :
4242
else
43-
echo "Could not read cache mtime - run 'kpunlock' again." >&2
43+
echo "Could not read cache mtime - run 'kpunlock' again." >&2
4444
exit 1
4545
fi
4646
age=$(( $(date +%s) - mtime ))
47-
if (( age > KPCACHE_TTL )); then
47+
if (( age > KP_TTL )); then
4848
rm -f "$CACHE"
49-
echo "Cache expired (>${KPCACHE_TTL}s) - run 'kpunlock' again." >&2
49+
echo "Cache expired (>${KP_TTL}s) - run 'kpunlock' again." >&2
5050
exit 1
5151
fi
5252
fi
@@ -62,6 +62,6 @@ extra_args=()
6262
[[ -n "$KEYFILE" ]] && extra_args+=(--key-file "$KEYFILE")
6363

6464
if ! keepassxc-cli show -q "${extra_args[@]}" "$@" "$DB" -- "$ENTRY" < "$CACHE"; then
65-
echo "keepassxc-cli failed - cache may be stale, try 'kpunlock'." >&2
65+
echo "keepassxc-cli failed - cache may be stale, try 'kpunlock'." >&2
6666
exit 1
6767
fi

bin/kplock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44
# Note: shred has no effect on tmpfs (RAM-backed, no physical blocks),
55
# so we just unlink - the kernel reclaims the pages.
66

7-
CONFIG="${KPCACHE_CONFIG:-$HOME/.config/kpcache/config}"
7+
CONFIG="${KP_CONFIG:-$HOME/.config/kpxc/config}"
88
if [[ -f "$CONFIG" ]]; then
99
# shellcheck source=/dev/null
1010
source "$CONFIG"
1111
fi
1212

13-
CACHE="${KPCACHE_FILE:-/dev/shm/$(id -u)-kpcache}"
13+
CACHE="${KP_CACHE:-/dev/shm/$(id -u)-kpxc}"
1414

1515
if [[ -f "$CACHE" ]]; then
1616
rm -f "$CACHE"
17-
echo "Cache cleared"
17+
echo "Cache cleared"
1818
else
1919
echo "(no cache)"
2020
fi

0 commit comments

Comments
 (0)