secret get ignores a failed write of the value to stdout and exits 0 #155

Closed
opened 2026-10-13 00:38:38 +02:00 by clawbot · 2 comments
Collaborator

Severity: major
A script that redirects secret get to a file sees success with a truncated or empty file; the stored secret is intact.

What happens: GetSecret and GetSecretWithVersion write the value with _, _ = cmd.OutOrStdout().Write(value.Bytes()), so a short write, a full disk, a closed stdout or a broken pipe is neither reported nor reflected in the exit status.

Reproduction, after secret init and echo v | secret add s:

SB_UNLOCK_PASSPHRASE=... secret get s > /dev/full; echo $?   # 0, no error
SB_UNLOCK_PASSPHRASE=... secret get s >&-; echo $?           # 0, no error

secret list has the same shape (_, _ = fmt.Fprintf(out, ...)), but get is the one whose output is the secret.

Where:

_, _ = cmd.OutOrStdout().Write(value.Bytes())
(GetSecret) and
_, _ = cmd.OutOrStdout().Write(value.Bytes())
(GetSecretWithVersion).

Correct behaviour: a write error is returned as the command's error, so the exit status is 1 and stderr says the value could not be written (a broken pipe is the usual exception: the reader went away, and reporting it is optional).

Definition of done

  • secret get s > /dev/full exits 1 with an error naming the write failure; get to a closed stdout exits 1.
  • A test runs get with stdout set to a writer that fails and requires a non-nil error; it fails before the fix.

Model: fable-5-1

Severity: major A script that redirects `secret get` to a file sees success with a truncated or empty file; the stored secret is intact. What happens: `GetSecret` and `GetSecretWithVersion` write the value with `_, _ = cmd.OutOrStdout().Write(value.Bytes())`, so a short write, a full disk, a closed stdout or a broken pipe is neither reported nor reflected in the exit status. Reproduction, after `secret init` and `echo v | secret add s`: ``` SB_UNLOCK_PASSPHRASE=... secret get s > /dev/full; echo $? # 0, no error SB_UNLOCK_PASSPHRASE=... secret get s >&-; echo $? # 0, no error ``` `secret list` has the same shape (`_, _ = fmt.Fprintf(out, ...)`), but `get` is the one whose output is the secret. Where: https://git.eeqj.de/sneak/secret/src/commit/ef0ae90768c8b18a829033b3ebd4cf98fcaeb3d1/internal/cli/secrets.go#L446 (`GetSecret`) and https://git.eeqj.de/sneak/secret/src/commit/ef0ae90768c8b18a829033b3ebd4cf98fcaeb3d1/internal/cli/secrets.go#L481 (`GetSecretWithVersion`). Correct behaviour: a write error is returned as the command's error, so the exit status is 1 and stderr says the value could not be written (a broken pipe is the usual exception: the reader went away, and reporting it is optional). **Definition of done** - `secret get s > /dev/full` exits 1 with an error naming the write failure; `get` to a closed stdout exits 1. - A test runs `get` with stdout set to a writer that fails and requires a non-nil error; it fails before the fix. Model: fable-5-1
sneak was assigned by clawbot 2026-10-13 00:38:38 +02:00
Author
Collaborator

Reassigned to clawbot: sneak ruled that nothing is a release candidate while a known bug is open (sneak/project-management#32).

Plan: as the issue says: any failed write of the value is returned as the command's error, exit 1.

Model: opus-5-5

Reassigned to clawbot: sneak ruled that nothing is a release candidate while a known bug is open (https://git.eeqj.de/sneak/project-management/issues/32). Plan: as the issue says: any failed write of the value is returned as the command's error, exit 1. Model: opus-5-5
sneak was unassigned by clawbot 2026-10-14 19:42:58 +02:00
clawbot self-assigned this 2026-10-14 19:42:58 +02:00
clawbot added this to the 1.0.0 milestone 2026-10-14 19:42:59 +02:00
Author
Collaborator

Reading taken on the closed-stdout item of the Definition of done: it cannot be met when the shell closes stdout before secret starts. The Go runtime opens /dev/null for a closed standard descriptor before any of secret runs, so the value goes there and secret get exits 0. Telling that apart from a deliberate redirect to /dev/null would need a check of how the descriptor was opened, which I did not build.

What the PR does: any failed write of the value is the command's error and it exits 1. That covers a full disk and a stdout that fails while secret runs, such as the closed file the new test uses.

Model: opus-5-5

Reading taken on the closed-stdout item of the Definition of done: it cannot be met when the shell closes stdout before `secret` starts. The Go runtime opens `/dev/null` for a closed standard descriptor before any of `secret` runs, so the value goes there and `secret get` exits 0. Telling that apart from a deliberate redirect to `/dev/null` would need a check of how the descriptor was opened, which I did not build. What the PR does: any failed write of the value is the command's error and it exits 1. That covers a full disk and a stdout that fails while `secret` runs, such as the closed file the new test uses. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/secret#155