A cross-vault secret mv whose source could not be removed reports "Moved" and exits 0, leaving the secret in both vaults #159

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

Severity: minor
A script cannot tell that the source is still there; nothing is lost, and removing the source by hand finishes the move.

What happens: moveSecretCrossVault copies every version into the destination vault, then removes the source; if that removal fails it prints a warning and the normal "Moved ..." line and returns nil, so the exit status is 0.

Reproduction (vaults default and work, source vault directory made read-only so the rename into a temporary directory fails):

echo v | secret add stuck
chmod 500 "$SB_SECRET_STATE_DIR/vaults.d/default"
secret mv default:stuck work; echo $?
# Warning: copied secret but failed to remove source: failed to create temporary directory for .../default/secrets.d/stuck: mkdir .../default/.tmp-...: permission denied
# Moved secret 'default:stuck' to 'work:stuck' (1 version(s))
# 0
chmod 700 "$SB_SECRET_STATE_DIR/vaults.d/default"
secret list -q                    # stuck   (still in default)

Where:

err = secret.RemoveDirAtomic(cli.fs, srcSecretDir)
(moveSecretCrossVault).

Correct behaviour: the command returns the removal error (exit 1) and says that the copy in the destination vault is complete and the source remains, so the user removes it with secret rm after checking; the README's "The source secret is deleted after successful copy" then holds or the command says it did not.

Definition of done

  • A cross-vault move whose source removal fails exits 1 with an error that names both the complete copy and the remaining source.
  • A test with a filesystem that fails the source removal requires a non-nil error and both copies present; it fails before the fix.

Model: fable-5-1

Severity: minor A script cannot tell that the source is still there; nothing is lost, and removing the source by hand finishes the move. What happens: `moveSecretCrossVault` copies every version into the destination vault, then removes the source; if that removal fails it prints a warning and the normal "Moved ..." line and returns nil, so the exit status is 0. Reproduction (vaults `default` and `work`, source vault directory made read-only so the rename into a temporary directory fails): ``` echo v | secret add stuck chmod 500 "$SB_SECRET_STATE_DIR/vaults.d/default" secret mv default:stuck work; echo $? # Warning: copied secret but failed to remove source: failed to create temporary directory for .../default/secrets.d/stuck: mkdir .../default/.tmp-...: permission denied # Moved secret 'default:stuck' to 'work:stuck' (1 version(s)) # 0 chmod 700 "$SB_SECRET_STATE_DIR/vaults.d/default" secret list -q # stuck (still in default) ``` Where: https://git.eeqj.de/sneak/secret/src/commit/ef0ae90768c8b18a829033b3ebd4cf98fcaeb3d1/internal/cli/secrets.go#L1068 (`moveSecretCrossVault`). Correct behaviour: the command returns the removal error (exit 1) and says that the copy in the destination vault is complete and the source remains, so the user removes it with `secret rm` after checking; the README's "The source secret is deleted after successful copy" then holds or the command says it did not. **Definition of done** - A cross-vault move whose source removal fails exits 1 with an error that names both the complete copy and the remaining source. - A test with a filesystem that fails the source removal requires a non-nil error and both copies present; it fails before the fix. Model: fable-5-1
sneak was assigned by clawbot 2026-10-13 00:39:06 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/secret#159