Copy a password without leaving a trace of it

The launcher's Copy Password command, built alone and last as the plan
required, because every line of it is the security design: the secret
travels rbw to wl-copy through a pipe -- never argv, never a file --
and the copy carries wl-clipboard's --sensitive hint, which vicinae's
clipboard history documents it ignores. That claim was not taken on
faith: a plain probe landed in the live history database and a
sensitive one did not, before any of this was written. A transient
timer clears the clipboard after thirty seconds. An unconfigured rbw
gets a setup message; a vault that locks between list and get gets an
honest failure instead of an empty copy claiming success. rbw joins
desktop-packages, and the contract pins the whole journey with a stub
vault, including that the secret never appears on a command line.
This commit is contained in:
Gabriel Brown
2026-08-21 19:38:38 -04:00
parent 4e978bf3b7
commit c02329ac3c
5 changed files with 125 additions and 13 deletions
+59 -1
View File
@@ -38,7 +38,7 @@ mkdir -p "$stub_dir"
# ── 1. Vicinae accepts every new command ─────────────────────────────────────
commands=(lock-screen suspend-system log-out reboot-system power-off
remind-me list-reminders pick-color
remind-me list-reminders pick-color copy-password
switch-window force-quit-window kill-process ssh-hosts recent-files)
for command in "${commands[@]}"; do
path="$scripts/$command"
@@ -195,6 +195,64 @@ DMENU_ANSWER=1 HOME="$fake_home" PATH="$stub_dir:$PATH" "$pick" recent >/dev/nul
grep -q 'xdg-open /tmp/older file.txt' "$calls" \
|| note 'the picked recent file was not opened with its URI decoded (newest-first order, %20 as space)'
# ── 6. Passwords: the secret's whole journey is a pipe ───────────────────────
#
# The rules that make Copy Password safe to ship at all:
# * the secret is never an argument to anything (argv is world-readable)
# * it reaches wl-copy with --sensitive, the hint vicinae's clipboard
# history documents it ignores -- without it, passwords land in a
# plaintext FTS database
# * a clipboard clear is scheduled the moment the copy happens
# * an unconfigured rbw degrades to a setup message, never an error
secret='s3cr3t-fixture-value'
cat >"$stub_dir/rbw" <<STUB
#!/usr/bin/env bash
printf 'rbw %s\n' "\$*" >>"$calls"
case "\$1" in
list) printf 'github\tgib\nrouter\t\n';;
get) printf '%s\n' '$secret';;
esac
STUB
chmod +x "$stub_dir/rbw"
cat >"$stub_dir/wl-copy" <<STUB
#!/usr/bin/env bash
printf 'wl-copy %s\n' "\$*" >>"$calls"
cat >"$work/wl-copy-stdin"
STUB
chmod +x "$stub_dir/wl-copy"
: >"$calls"; : >"$work/wl-copy-stdin"
DMENU_ANSWER=0 PATH="$stub_dir:$PATH" "$pick" password >/dev/null 2>&1 \
|| note 'copying the first password entry failed'
grep -q "$secret" "$calls" \
&& note 'the secret appears on a command line'
grep -qx "$secret" "$work/wl-copy-stdin" \
|| note 'the secret does not reach wl-copy on stdin'
grep -q 'wl-copy.*--sensitive' "$calls" \
|| note 'the copy is not marked sensitive, so it lands in clipboard history'
grep -q 'rbw get -- github gib' "$calls" \
|| note 'the picked entry name and user do not reach rbw get, flag-safe'
grep -qE 'systemd-run .*--on-active=30s .*wl-copy --clear' "$calls" \
|| note 'no clipboard clear is scheduled after the copy'
# Escape copies nothing.
: >"$calls"
PATH="$stub_dir:$PATH" "$pick" password >/dev/null 2>&1
grep -q 'wl-copy' "$calls" && note 'Escape from the password list still copied something'
# Unconfigured rbw is a setup hint, not a failure.
cat >"$stub_dir/rbw" <<'STUB'
#!/usr/bin/env bash
exit 1
STUB
chmod +x "$stub_dir/rbw"
: >"$calls"
PATH="$stub_dir:$PATH" "$pick" password >/dev/null 2>&1 \
|| note 'an unconfigured rbw is treated as a crash instead of a setup hint'
grep -q 'not set up' "$calls" \
|| note 'an unconfigured rbw does not explain how to set it up'
if (( ${#findings[@]} > 0 )); then
printf 'launcher commands contract: %d finding(s)\n' "${#findings[@]}" >&2
printf ' - %s\n' "${findings[@]}" >&2