A gpg failure is reported as only its exit status; gpg's reason on stderr is dropped #146

Open
opened 2026-10-13 00:30:39 +02:00 by clawbot · 0 comments
Collaborator

Severity: minor
When gpg fails, the user is told only its exit status: GPG decryption failed: exit status 2. gpg's own reason ("No secret key", "Inappropriate ioctl for device" from pinentry without GPG_TTY, a cancelled pinentry, an expired key) is written to stderr, which the program captures and throws away, so a PGP-unlocker failure cannot be diagnosed from the message, and the advice appended to it ("still opens with its mnemonic") points the wrong way.

Reproduction (vault with a PGP unlocker as current; a GNUPGHOME that lacks the secret key stands in for any gpg failure):

GNUPGHOME=/tmp/empty secret get foo
# Error: failed to unlock vault: failed to get long-term key: failed to get unlocker identity: failed to decrypt age private key with GPG: GPG decryption failed: exit status 2; the vault 'default' still opens with its mnemonic: ...

gpg --decrypt itself says gpg: decryption failed: No secret key. Encryption (unlocker add pgp) and the fingerprint lookup report the same way.

Where:

output, err := cmd.Output()
(gpgDecryptDefault), L439 (gpgEncryptDefault), L386 (ResolveGPGKeyFingerprint): each uses cmd.Output(), which stores gpg's stderr in the *exec.ExitError but the %w message does not include it.

Correct behaviour: the error includes gpg's stderr (trimmed), for example GPG decryption failed: exit status 2: gpg: decryption failed: No secret key.

Definition of done

  • A gpg failure in decrypt, encrypt and key lookup names gpg's reason from its stderr in the error.
  • A regression test runs the real gpg against an empty GNUPGHOME through GPGDecryptFunc and requires the error to contain gpg's message; it fails before the fix.

Model: fable-5-1

Severity: minor When `gpg` fails, the user is told only its exit status: `GPG decryption failed: exit status 2`. gpg's own reason ("No secret key", "Inappropriate ioctl for device" from pinentry without `GPG_TTY`, a cancelled pinentry, an expired key) is written to stderr, which the program captures and throws away, so a PGP-unlocker failure cannot be diagnosed from the message, and the advice appended to it ("still opens with its mnemonic") points the wrong way. Reproduction (vault with a PGP unlocker as current; a `GNUPGHOME` that lacks the secret key stands in for any gpg failure): ``` GNUPGHOME=/tmp/empty secret get foo # Error: failed to unlock vault: failed to get long-term key: failed to get unlocker identity: failed to decrypt age private key with GPG: GPG decryption failed: exit status 2; the vault 'default' still opens with its mnemonic: ... ``` `gpg --decrypt` itself says `gpg: decryption failed: No secret key`. Encryption (`unlocker add pgp`) and the fingerprint lookup report the same way. Where: https://git.eeqj.de/sneak/secret/src/commit/ef0ae90768c8b18a829033b3ebd4cf98fcaeb3d1/internal/secret/pgpunlocker.go#L452 (`gpgDecryptDefault`), L439 (`gpgEncryptDefault`), L386 (`ResolveGPGKeyFingerprint`): each uses `cmd.Output()`, which stores gpg's stderr in the `*exec.ExitError` but the `%w` message does not include it. Correct behaviour: the error includes gpg's stderr (trimmed), for example `GPG decryption failed: exit status 2: gpg: decryption failed: No secret key`. **Definition of done** - A gpg failure in decrypt, encrypt and key lookup names gpg's reason from its stderr in the error. - A regression test runs the real `gpg` against an empty `GNUPGHOME` through `GPGDecryptFunc` and requires the error to contain gpg's message; it fails before the fix. Model: fable-5-1
sneak was assigned by clawbot 2026-10-13 00:30:39 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/secret#146