Running secret init again over an existing vault makes its secrets undecryptable #74

Open
opened 2026-10-03 17:52:28 +02:00 by clawbot · 1 comment
Collaborator

Found in the review of #69. Present on next.

Problem

secret init calls vault.CreateVault(..., "default") (internal/cli/init.go, setupDefaultVault), and CreateVault (internal/vault/management.go) does not check whether the vault already exists: it MkdirAlls the directories, picks a derivation index, overwrites the vault metadata, and init then overwrites the passphrase unlocker and longterm.age. Every existing secret stays encrypted to the old long-term key, which the vault no longer records, so secret get fails for all of them. With a different mnemonic the old key is gone for good; with the same one, recovery needs the old derivation index, which the overwritten metadata no longer holds. The same applies to secret vault create with an existing vault name.

Definition of done

  • secret init refuses when the default vault already exists, and secret vault create NAME refuses when NAME exists, each with a clear error naming the vault, before anything is written. Use the existing "already exists" sentinel if there is one.
  • The check is in CreateVault itself, so no caller can bypass it.
  • Tests: init twice, and vault create with an existing name, each leave the state directory byte-for-byte unchanged (record every file and directory with contents before and after) and the existing secret still decrypts.
  • TODO.md updated in the same commit.

Sequencing

After #69 lands (it changes CreateVault and init).

Model: opus-5-5

Found in the review of https://git.eeqj.de/sneak/secret/pulls/69. Present on `next`. ## Problem `secret init` calls `vault.CreateVault(..., "default")` (`internal/cli/init.go`, `setupDefaultVault`), and `CreateVault` (`internal/vault/management.go`) does not check whether the vault already exists: it `MkdirAll`s the directories, picks a derivation index, overwrites the vault metadata, and `init` then overwrites the passphrase unlocker and `longterm.age`. Every existing secret stays encrypted to the old long-term key, which the vault no longer records, so `secret get` fails for all of them. With a different mnemonic the old key is gone for good; with the same one, recovery needs the old derivation index, which the overwritten metadata no longer holds. The same applies to `secret vault create` with an existing vault name. ## Definition of done - `secret init` refuses when the default vault already exists, and `secret vault create NAME` refuses when `NAME` exists, each with a clear error naming the vault, before anything is written. Use the existing "already exists" sentinel if there is one. - The check is in `CreateVault` itself, so no caller can bypass it. - Tests: `init` twice, and `vault create` with an existing name, each leave the state directory byte-for-byte unchanged (record every file and directory with contents before and after) and the existing secret still decrypts. - `TODO.md` updated in the same commit. ## Sequencing After https://git.eeqj.de/sneak/secret/pulls/69 lands (it changes `CreateVault` and `init`). Model: opus-5-5
clawbot added the critical label 2026-10-03 17:52:28 +02:00
Author
Collaborator

Labelled critical: running secret init a second time (or secret vault create with an existing name) overwrites the vault key records, and every secret already stored becomes undecryptable for its owner.

Model: opus-5-5

Labelled critical: running `secret init` a second time (or `secret vault create` with an existing name) overwrites the vault key records, and every secret already stored becomes undecryptable for its owner. 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#74