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:
@@ -7,6 +7,7 @@
|
||||
# panama-pick process pick a process by CPU; SIGTERM it
|
||||
# panama-pick ssh pick a Host from ~/.ssh/config; open a session
|
||||
# panama-pick recent pick a recently used file; open it
|
||||
# panama-pick password pick a Bitwarden entry; copy it, concealed
|
||||
#
|
||||
# One helper rather than five scripts because every subcommand is the same
|
||||
# sentence: build a list, let dmenu pick a line, act on the index. dmenu is
|
||||
@@ -96,8 +97,46 @@ PY
|
||||
[[ -n "$idx" ]] || exit 0
|
||||
exec xdg-open "$(line_at "$idx" <<<"$entries")"
|
||||
;;
|
||||
password)
|
||||
# The security shape, in one place:
|
||||
# * the secret travels rbw -> pipe -> wl-copy; it is never an argument
|
||||
# and never a file (argv is world-readable, files outlive intentions)
|
||||
# * --sensitive offers the x-kde-passwordManagerHint MIME, which
|
||||
# vicinae's clipboard history documents it ignores -- verified against
|
||||
# the live history before this was written
|
||||
# * the clipboard clears itself after 30 seconds, from a transient timer
|
||||
# so it happens even if this shell is long gone
|
||||
if ! command -v rbw >/dev/null 2>&1; then
|
||||
notify-send "Passwords" "rbw is not installed" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
if ! entries="$(rbw list --fields name,user 2>/dev/null)" || [[ -z "$entries" ]]; then
|
||||
notify-send "Passwords" \
|
||||
"rbw is not set up. Run: rbw config set email <you>; rbw register; rbw login" \
|
||||
2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
idx="$(awk -F'\t' '{ if ($2 != "") printf "%s (%s)\n", $1, $2; else print $1 }' <<<"$entries" \
|
||||
| menu "Copy password" 'Bitwarden ({count})')" || exit 0
|
||||
[[ -n "$idx" ]] || exit 0
|
||||
picked="$(line_at "$idx" <<<"$entries")"
|
||||
name="$(cut -f1 <<<"$picked")"
|
||||
user="$(cut -f2 <<<"$picked")"
|
||||
# `--` keeps an entry named like a flag from parsing as one; the pipeline
|
||||
# status catches a vault that locked between list and get, so a failed
|
||||
# read is reported instead of an empty copy claiming success.
|
||||
if ! rbw get -- "$name" ${user:+"$user"} | wl-copy --trim-newline --sensitive; then
|
||||
notify-send "Passwords" "Could not read \"$name\" — is the vault locked?" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
systemd-run --user --collect --on-active=30s \
|
||||
--unit="panama-clip-clear-$(date +%s)-$RANDOM" \
|
||||
wl-copy --clear >/dev/null 2>&1 || true
|
||||
notify-send --icon=dialog-password-symbolic \
|
||||
"Password copied" "\"$name\" — the clipboard clears in 30 seconds" 2>/dev/null || true
|
||||
;;
|
||||
*)
|
||||
echo 'usage: panama-pick window|quit-window|process|ssh|recent' >&2
|
||||
echo 'usage: panama-pick window|quit-window|process|ssh|recent|password' >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Copy Password
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Pick a Bitwarden entry; the password lands on the clipboard, concealed from history, and clears in 30 seconds.
|
||||
# @vicinae.keywords ["password", "bitwarden", "vault", "credentials", "login", "secret"]
|
||||
|
||||
exec "$HOME/.config/quickshell/scripts/panama-pick" password
|
||||
@@ -127,16 +127,17 @@ The list-picking half. One bash helper, not a compiled extension: `vicinae dmenu
|
||||
### Task 8: Bitwarden via rbw — alone, last, carefully
|
||||
|
||||
**Files:**
|
||||
- Modify: `setup/packages/` (add `rbw`)
|
||||
- Modify: `config/local/share/vicinae/extensions/panama-desktop/` (src/passwords.tsx)
|
||||
- Create: `tests/setup/rbw-command-contract`
|
||||
- Modify: `setup/packages/desktop-packages` (add `rbw`)
|
||||
- Modify: `config/dot/quickshell/scripts/panama-pick` (password subcommand)
|
||||
- Create: `config/local/share/vicinae/scripts/copy-password`
|
||||
- Modify: `tests/setup/launcher-commands-contract` (password section)
|
||||
|
||||
**Steps:**
|
||||
- [ ] `rbw` added to a package list; first-run setup documented (`rbw config set email`, `rbw login`) and surfaced as a friendly message when unconfigured
|
||||
- [ ] List entries from `rbw list`; copy password on select via `rbw get` piped to `wl-copy`, never argv, never a temp file
|
||||
- [ ] Clipboard clears after 30 seconds (`wl-copy --clear` scheduled with `systemd-run --user --on-active=30s` or vicinae's own clipboard hygiene if it offers one — check first)
|
||||
- [ ] Contract with a stub rbw: copy path never leaks the secret into argv or the environment of anything but wl-copy; unconfigured rbw degrades to the setup message
|
||||
- [ ] Review pass on this task's diff before it merges, separate from writing it
|
||||
- [x] `rbw` added to a package list; first-run setup documented (`rbw config set email`, `rbw login`) and surfaced as a friendly message when unconfigured
|
||||
- [x] List entries from `rbw list`; copy password on select via `rbw get` piped to `wl-copy`, never argv, never a temp file
|
||||
- [x] Clipboard clears after 30 seconds (`wl-copy --clear` scheduled with `systemd-run --user --on-active=30s` or vicinae's own clipboard hygiene if it offers one — check first)
|
||||
- [x] Contract with a stub rbw: copy path never leaks the secret into argv or the environment of anything but wl-copy; unconfigured rbw degrades to the setup message
|
||||
- [x] Review pass on this task's diff before it merges, separate from writing it
|
||||
|
||||
### Task 9: Documentation and closeout
|
||||
|
||||
@@ -146,6 +147,8 @@ The list-picking half. One bash helper, not a compiled extension: `vicinae dmenu
|
||||
- Modify: this file — Findings section, boxes ticked
|
||||
|
||||
**Steps:**
|
||||
- [ ] README's Vicinae row reflects the new command families
|
||||
- [ ] Every new file carries the why-comment the repo expects; no `.sh` extensions anywhere
|
||||
- [ ] Full `panama test` run is green; README count matches
|
||||
- [x] README's Vicinae row reflects the new command families
|
||||
- [x] Every new file carries the why-comment the repo expects; no `.sh` extensions anywhere
|
||||
- [x] Full `panama test` run is green; README count matches
|
||||
|
||||
**Task 8 findings (2026-08-21):** `wl-copy --sensitive` (wl-clipboard 2.2) offers the password-manager hint MIME, and vicinae's clipboard docs commit to ignoring it — verified live against the real history database (a plain probe was recorded, a sensitive one was not) before the command was written. The clipboard clear is a 30s transient timer. Review pass caught and fixed a silent empty copy on a locked vault and a flag-shaped entry name.
|
||||
|
||||
@@ -72,6 +72,9 @@ openssl-devel
|
||||
opus-devel
|
||||
papers
|
||||
python3-dnf-plugin-versionlock
|
||||
# The launcher's Copy Password command; talks to the same Bitwarden account
|
||||
# the desktop app signs into. See panama-pick.
|
||||
rbw
|
||||
rocm-opencl
|
||||
# The Snapshots page is snapper end to end; see scripts/panama-snapshots.
|
||||
snapper
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user