Skip to content

feat(vault): clipboard hardening — clear-on-lock, backend trait, OSC52, configurable interval#5

Merged
UnbreakableMJ merged 1 commit into
mainfrom
clipboard-hardening
Jun 13, 2026
Merged

feat(vault): clipboard hardening — clear-on-lock, backend trait, OSC52, configurable interval#5
UnbreakableMJ merged 1 commit into
mainfrom
clipboard-hardening

Conversation

@UnbreakableMJ

Copy link
Copy Markdown
Contributor

Summary

Closes the secret-can-outlive-the-agent limitation tracked since the M5 copy slice, and delivers PRD §7.5 in its architecturally honest shape (the OSC52 fallback can only be client-side).

Clear-on-lock sweep

  • The agent remembers the last value it copied (Zeroizing, dropped once a timer clears it), and lock() now sweeps it off the clipboard.
  • Covers vault lock, Quit/stop-agent, idle-lock, and a new SIGTERM handler (PRD §7.3: lock → sweep → remove socket → exit).
  • Keeps the only-if-still-ours rule — a newer copy by the user is never clobbered.

Backend trait (PRD §7.5)

  • New vault-agent/src/clipboard.rs defines a Backend trait; arboard is the one system implementation.
  • Tests inject a fake clipboard, so the sweep logic is now exercised end-to-end on headless CI.
  • Status gains a serde-defaulted clipboard_backend field (surfaced in vault status, human + --json); old-agent frames still decode (regression-tested).

OSC52 fallback — client-side, by design

  • OSC52 copies by escaping to the user's terminal, which a detached daemon fundamentally cannot do — so it lives in the TUI.
  • When the agent declines with the new typed Error::ClipboardUnavailable, the TUI fetches the value (id-targeted Get — the one path where the secret crosses the local UDS, same as vault get) and emits the OSC52 sequence itself, with tmux DCS-passthrough wrapping when $TMUX is set (set-clipboard on required).
  • The TUI runs its own 30s timed clear (run loop races input against the deadline) and sweeps on quit; a generated password falls back without any Get since it's already local.

Configurable interval

  • vault-agent --clipboard-clear-secs N$VAULT_CLIPBOARD_CLEAR_SECS → 30 (0 disables); Request::Copy/CopyText still override per call.
  • New Response::Copied { clear_after_secs } reports the effective window, so the TUI toast shows the agent's real interval instead of a hardcoded 30.

Tests

Fake-backend units (sweep-on-lock, never-clobber-newer-copy, timer/sweep-marker interplay), Status backend-name reporting, interval-resolution precedence, OSC52 sequence/clear/tmux-wrapping units, and the old-agent Status decode regression. Workspace: 115 tests green, clippy -D warnings (pedantic+nursery) clean, both headless builds green, cargo deny clean. Verified live: SIGTERM sweep-and-exit, clipboard_backend in status output.

🤖 Generated with Claude Code

…2, configurable interval

Close the secret-can-outlive-the-agent limitation tracked since the M5
copy slice, and deliver PRD §7.5 in its architecturally honest shape.

Clear-on-lock sweep: the agent remembers the last value it copied
(Zeroizing, dropped once a timer clears it), and lock() now sweeps it
off the clipboard — covering `vault lock`, Quit/stop-agent, idle-lock,
and a new SIGTERM handler (PRD §7.3: lock, sweep, remove the socket,
exit). The sweep keeps the only-if-still-ours rule, so a newer copy by
the user is never clobbered.

Backend trait (PRD §7.5): new vault-agent/src/clipboard.rs defines a
Backend trait (arboard is the system implementation) so tests inject a
fake clipboard and the sweep logic is exercised on headless CI, and a
config-selected backend can slot in later. Status gains a
serde-defaulted clipboard_backend field (surfaced in `vault status`);
old-agent frames still decode.

OSC52 fallback (client-side, by design): OSC52 escapes to the user's
terminal — something the detached agent cannot do — so it lives in the
TUI. When the agent declines with the new typed
Error::ClipboardUnavailable, the TUI fetches the value (id-targeted
Get, the one path where the secret crosses the local UDS, same as
`vault get`) and emits the OSC52 sequence itself, tmux DCS-passthrough
wrapped when $TMUX is set. The TUI runs its own 30s timed clear and
sweeps on quit; a generated password falls back without any Get.

Configurable interval: vault-agent --clipboard-clear-secs N (then
$VAULT_CLIPBOARD_CLEAR_SECS, then 30; 0 disables) sets the agent
default; Request::Copy/CopyText still override per call. The new
Response::Copied { clear_after_secs } reports the effective window, so
the TUI toast shows the agent's real interval instead of a hardcoded 30.

Tests: fake-backend units for sweep-on-lock, never-clobber-newer-copy,
and timer/sweep marker interplay; Status backend-name reporting;
interval-resolution precedence; OSC52 sequence/clear/tmux-wrapping
units; and the old-agent Status decode regression. Verified live:
SIGTERM sweep-and-exit, clipboard_backend in status output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@UnbreakableMJ
UnbreakableMJ merged commit d0ad250 into main Jun 13, 2026
8 checks passed
@UnbreakableMJ
UnbreakableMJ deleted the clipboard-hardening branch June 13, 2026 08:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant