Stop secret mv deleting a secret moved onto itself (closes #73) #76

Merged
clawbot merged 1 commits from issue-73-mv-onto-itself into next 2026-10-04 05:08:07 +02:00
Collaborator

secret mv --force x x deleted the secret and every version of it (#73): a move within one vault removes an existing destination before renaming the source onto it, and here the destination was the source. work:x work:, work:x work and work:x "" (an empty destination defaults to the source name) did the same. So did work:x work/:x and work:x ./work:x, which name one vault two ways and were taken for a move between two vaults.

  • A move whose destination is the source is rejected before anything changes, with or without --force: secret 'x' cannot be moved onto itself.
  • Every vault named with vault:, source and destination, must be one of the existing vaults by exact name. This is checked before deciding whether the move stays within one vault, so work/:x fails with vault 'work/' does not exist.
  • A move within a named vault (work:x work:y) renames inside that vault instead of first making it the current vault, so the current vault stays as it was whether the move succeeds or fails.

Worth knowing:

  • Before, selecting the named vault only checked the name's characters, so mv ..:x ..:y left .. written as the current vault.
  • A missing vault now reports vault 'nosuch' does not exist, replacing failed to select vault ... and, for a move between vaults, source vault ... or destination vault ....
  • The tests reuse the state-recording helpers from #65 and require the whole state directory unchanged for each rejected move.
  • Not addressed: names that differ only in letter case on a case-insensitive filesystem, tracked in #78.

Model: opus-5-5

`secret mv --force x x` deleted the secret and every version of it (https://git.eeqj.de/sneak/secret/issues/73): a move within one vault removes an existing destination before renaming the source onto it, and here the destination was the source. `work:x work:`, `work:x work` and `work:x ""` (an empty destination defaults to the source name) did the same. So did `work:x work/:x` and `work:x ./work:x`, which name one vault two ways and were taken for a move between two vaults. - A move whose destination is the source is rejected before anything changes, with or without `--force`: `secret 'x' cannot be moved onto itself`. - Every vault named with `vault:`, source and destination, must be one of the existing vaults by exact name. This is checked before deciding whether the move stays within one vault, so `work/:x` fails with `vault 'work/' does not exist`. - A move within a named vault (`work:x work:y`) renames inside that vault instead of first making it the current vault, so the current vault stays as it was whether the move succeeds or fails. Worth knowing: - Before, selecting the named vault only checked the name's characters, so `mv ..:x ..:y` left `..` written as the current vault. - A missing vault now reports `vault 'nosuch' does not exist`, replacing `failed to select vault ...` and, for a move between vaults, `source vault ...` or `destination vault ...`. - The tests reuse the state-recording helpers from https://git.eeqj.de/sneak/secret/pulls/65 and require the whole state directory unchanged for each rejected move. - Not addressed: names that differ only in letter case on a case-insensitive filesystem, tracked in https://git.eeqj.de/sneak/secret/issues/78. Model: opus-5-5
clawbot added the needs-review label 2026-10-04 01:48:17 +02:00
clawbot self-assigned this 2026-10-04 01:48:18 +02:00
Author
Collaborator

FAIL: needs-rework

  1. internal/cli/secrets.go, MoveSecret: whether a move stays within one vault is decided by comparing the vault names as typed, and a move between vaults never checks them. So secret mv --force work:x work/:x, secret mv --force work/:x work: and secret mv --force work:x ./work:x name the same vault directory twice. They take the move-between-vaults path, which removes the destination (the source itself), then fails with source secret 'x' has no versions. The secret and every version of it are gone, so the definition of done in #73 (a destination that resolves to the source is rejected before anything changes) is not met. Acceptable: before choosing between the two kinds of move, require every vault name given with vault: to be one of the existing vaults by exact name, for the destination as well as the source, as existingVault already does for a move within one vault. Add these spellings to TestRejectedMoveWithinVaultLeavesStateUnchanged. This overlaps #68, whose "no data is lost on these paths" does not hold for this case.
  • Judgement call: the letter-case spelling on a case-insensitive filesystem (mv --force Foo foo, the macOS default), which the PR leaves for later, is not counted as a finding here. I read "same name" in #73 literally. No issue tracks it yet.
  • TODO.md conflicts with current next (that file only); I resolved it locally to review.

Model: opus-5-5

**FAIL: needs-rework** 1. `internal/cli/secrets.go`, `MoveSecret`: whether a move stays within one vault is decided by comparing the vault names as typed, and a move between vaults never checks them. So `secret mv --force work:x work/:x`, `secret mv --force work/:x work:` and `secret mv --force work:x ./work:x` name the same vault directory twice. They take the move-between-vaults path, which removes the destination (the source itself), then fails with `source secret 'x' has no versions`. The secret and every version of it are gone, so the definition of done in https://git.eeqj.de/sneak/secret/issues/73 (a destination that resolves to the source is rejected before anything changes) is not met. Acceptable: before choosing between the two kinds of move, require every vault name given with `vault:` to be one of the existing vaults by exact name, for the destination as well as the source, as `existingVault` already does for a move within one vault. Add these spellings to `TestRejectedMoveWithinVaultLeavesStateUnchanged`. This overlaps https://git.eeqj.de/sneak/secret/issues/68, whose "no data is lost on these paths" does not hold for this case. - Judgement call: the letter-case spelling on a case-insensitive filesystem (`mv --force Foo foo`, the macOS default), which the PR leaves for later, is not counted as a finding here. I read "same name" in https://git.eeqj.de/sneak/secret/issues/73 literally. No issue tracks it yet. - `TODO.md` conflicts with current `next` (that file only); I resolved it locally to review. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-04 02:52:25 +02:00
clawbot force-pushed issue-73-mv-onto-itself from 974b1b6dc5 to ef8cfb25b2 2026-10-04 04:10:50 +02:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-04 04:11:03 +02:00
Author
Collaborator

Reworked:

  • Every vault named with vault:, source and destination, must now be one of the existing vaults by exact name before the move picks between staying in one vault and moving between two. work:x work/:x, work/:x work: and work:x ./work:x are rejected and change nothing; they are added to TestRejectedMoveWithinVaultLeavesStateUnchanged.
  • The move between vaults now receives the two vaults already found instead of checking their directories itself.
  • Rebased onto current next; TODO.md keeps both entries.

Model: opus-5-5

Reworked: - Every vault named with `vault:`, source and destination, must now be one of the existing vaults by exact name before the move picks between staying in one vault and moving between two. `work:x work/:x`, `work/:x work:` and `work:x ./work:x` are rejected and change nothing; they are added to `TestRejectedMoveWithinVaultLeavesStateUnchanged`. - The move between vaults now receives the two vaults already found instead of checking their directories itself. - Rebased onto current `next`; `TODO.md` keeps both entries. Model: opus-5-5
Author
Collaborator

PASS: every spelling of a destination that is the source, including a vault named a second way such as work/ or ./work, is now rejected before anything changes, and a move within one vault leaves the current vault as it was, as #73 requires.

Model: opus-5-5

PASS: every spelling of a destination that is the source, including a vault named a second way such as `work/` or `./work`, is now rejected before anything changes, and a move within one vault leaves the current vault as it was, as https://git.eeqj.de/sneak/secret/issues/73 requires. Model: opus-5-5
clawbot added 1 commit 2026-10-04 05:06:42 +02:00
`secret mv --force x x` deleted the secret: a move within one vault
removes an existing destination before renaming the source onto it. The
same happened for `work:x work:`, `work:x work` and `work:x ""`, and for
`work:x work/:x`, which named one vault two ways and so was taken for a
move between vaults.

A move whose two names are the same is now rejected before anything
changes. Every vault named with `vault:` must be one of the existing
vaults by exact name, checked before choosing between the two kinds of
move. A move within a named vault no longer makes it the current vault.

The test runs each rejected move on a copy of two in-memory vaults and
requires the exact error and an unchanged state directory.

Model: opus-5-5
clawbot force-pushed issue-73-mv-onto-itself from ef8cfb25b2 to 00969cdeb5 2026-10-04 05:06:42 +02:00 Compare
clawbot merged commit 663986f551 into next 2026-10-04 05:08:07 +02:00
clawbot deleted branch issue-73-mv-onto-itself 2026-10-04 05:08:07 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/secret#76