From 730877823eaf48ad3b708d7cec9c73667edc5caa Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Fri, 2 Oct 2026 17:27:07 +0100 Subject: [PATCH] Absorb: ask for the passphrase once the archive starts arriving With gpg --decrypt x.tar.gpg | vault absorb, vault printed its prompt while gpg was still writing to the terminal, which buried it, so the command looked hung. Peek at standard input first: the upstream program has finished with the terminal by the time data arrives, and an empty pipe now fails without asking at all. Co-Authored-By: Claude Opus 5.5 --- docs/guide/vault-guide.tex | 6 +++++- docs/vault-absorb.1 | 8 ++++++-- main.go | 10 +++++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/guide/vault-guide.tex b/docs/guide/vault-guide.tex index 54ac788..ff0a780 100644 --- a/docs/guide/vault-guide.tex +++ b/docs/guide/vault-guide.tex @@ -581,7 +581,11 @@ vault absorb old < archive.tar.gz Each file is encrypted straight from the stream, so nothing is unpacked to disk first. Directories need no entry of their own; links, devices and any -name that tries to climb out with \file{..} are skipped with a note. If the +name that tries to climb out with \file{..} are skipped with a note. +vault asks for your passphrase only once the archive starts arriving, so +when it comes out of something that asks first, like +\cmd{gpg --decrypt secrets.tar.gpg | vault absorb}, gpg's prompt comes +and goes before vault's appears. If the archive stops halfway through a file, that file isn't stored and vault stops; everything before it is already safely in. diff --git a/docs/vault-absorb.1 b/docs/vault-absorb.1 index 4d121f3..65d69e4 100644 --- a/docs/vault-absorb.1 +++ b/docs/vault-absorb.1 @@ -35,8 +35,12 @@ or is dropped. .PP The passphrase prompt reads from the terminal, not standard input, so -it still works with an archive piped in. The vault is locked exclusively -for the whole run. +it still works with an archive piped in. It waits until the archive starts +arriving, so a program feeding the pipe, such as +.BR gpg (1) +asking for its own passphrase, has finished with the terminal first. +An empty pipe fails without asking at all. +The vault is locked exclusively for the whole run. .SH OPTIONS .TP .BI \-\-key " PATH" diff --git a/main.go b/main.go index 179aa43..2e14587 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ package main import ( + "bufio" "errors" "flag" "fmt" @@ -158,10 +159,17 @@ func main() { if len(pos) == 1 { dest = strings.Trim(pos[0], "/") } + // Wait for the archive to start arriving before asking for the + // passphrase, so a program feeding the pipe (gpg, ssh) has finished + // with the terminal and cannot bury the prompt. + in := bufio.NewReader(os.Stdin) + if _, err := in.Peek(1); err != nil { + fail("nothing to absorb on standard input") + } s := open(*keyPath, *vaultDir, true) cat, err := s.catalogue() check(err) - check(absorb(s, cat, os.Stdin, dest)) + check(absorb(s, cat, in, dest)) case "list", "ls": want(0, 1) prefix := ""