Severity: critical
The vault's long-term key is derived from the mnemonic text exactly as typed, so a vault made with a mnemonic that had an extra space cannot be opened with the same words typed normally, and the recovery the README promises ("the vault still opens with its mnemonic") fails for it.
What happens: secret init and secret vault create validate the mnemonic with bip39.IsMnemonicValid, which splits on any whitespace, but then derive the key from the raw string: bip39.NewSeed feeds the bytes as given into PBKDF2. Two spaces between words, or a trailing space or tab (easy in SB_SECRET_MNEMONIC="..." or a pasted line), give a different seed from the single-spaced sentence BIP39 defines, so the vault's key matches no other BIP39 tool's and not the words the user wrote down.
Reproduction (M is a valid 12-word mnemonic, M2 the same words with two spaces after the first word):
SB_UNLOCK_PASSPHRASE=pw SB_SECRET_MNEMONIC="$M2" secret init
echo -n x | secret add db/pass
SB_SECRET_MNEMONIC="$M" secret unlocker add passphrase
# Error: ... derived public key does not match vault: mnemonic may be incorrect
SB_SECRET_MNEMONIC="$M" secret get db/pass # same error
A second state directory initialised with M gets a different pub.age at the same derivation index 0.
(validation splits on strings.Fields). The same raw string reaches deriveLongTermKeyFromMnemonic and GetNextDerivationIndex.
Correct behaviour: derive from the BIP39 sentence, the words joined by single spaces, NFKD-normalised, wherever the mnemonic is used (init, vault create, vault import, unlocker add, every unlock); the words as typed with any whitespace then always give the same key.
Definition of done
A mnemonic typed with extra or trailing whitespace creates a vault with the same key as the single-spaced sentence, and opens it.
A regression test derives a key from "w1 w2 ... " and from "w1 w2 ..." and requires the same identity; it fails before the fix.
Model: fable-5-1
Severity: critical
The vault's long-term key is derived from the mnemonic text exactly as typed, so a vault made with a mnemonic that had an extra space cannot be opened with the same words typed normally, and the recovery the README promises ("the vault still opens with its mnemonic") fails for it.
What happens: `secret init` and `secret vault create` validate the mnemonic with `bip39.IsMnemonicValid`, which splits on any whitespace, but then derive the key from the raw string: `bip39.NewSeed` feeds the bytes as given into PBKDF2. Two spaces between words, or a trailing space or tab (easy in `SB_SECRET_MNEMONIC="..."` or a pasted line), give a different seed from the single-spaced sentence BIP39 defines, so the vault's key matches no other BIP39 tool's and not the words the user wrote down.
Reproduction (`M` is a valid 12-word mnemonic, `M2` the same words with two spaces after the first word):
```
SB_UNLOCK_PASSPHRASE=pw SB_SECRET_MNEMONIC="$M2" secret init
echo -n x | secret add db/pass
SB_SECRET_MNEMONIC="$M" secret unlocker add passphrase
# Error: ... derived public key does not match vault: mnemonic may be incorrect
SB_SECRET_MNEMONIC="$M" secret get db/pass # same error
```
A second state directory initialised with `M` gets a different `pub.age` at the same derivation index 0.
Where: https://git.eeqj.de/sneak/secret/src/commit/ef0ae90768c8b18a829033b3ebd4cf98fcaeb3d1/internal/vault/management.go#L169 (`processMnemonicForVault` passes the raw string on), https://git.eeqj.de/sneak/secret/src/commit/ef0ae90768c8b18a829033b3ebd4cf98fcaeb3d1/pkg/agehd/agehd.go#L77 (`DeriveEntropy`), https://git.eeqj.de/sneak/secret/src/commit/ef0ae90768c8b18a829033b3ebd4cf98fcaeb3d1/internal/bip39/bip39.go#L197 (`NewSeed`) against https://git.eeqj.de/sneak/secret/src/commit/ef0ae90768c8b18a829033b3ebd4cf98fcaeb3d1/internal/bip39/bip39.go#L274 (validation splits on `strings.Fields`). The same raw string reaches `deriveLongTermKeyFromMnemonic` and `GetNextDerivationIndex`.
Correct behaviour: derive from the BIP39 sentence, the words joined by single spaces, NFKD-normalised, wherever the mnemonic is used (`init`, `vault create`, `vault import`, `unlocker add`, every unlock); the words as typed with any whitespace then always give the same key.
**Definition of done**
- A mnemonic typed with extra or trailing whitespace creates a vault with the same key as the single-spaced sentence, and opens it.
- A regression test derives a key from `"w1 w2 ... "` and from `"w1 w2 ..."` and requires the same identity; it fails before the fix.
Model: fable-5-1
clawbot
added this to the 1.0.0 milestone 2026-10-13 00:29:17 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: critical
The vault's long-term key is derived from the mnemonic text exactly as typed, so a vault made with a mnemonic that had an extra space cannot be opened with the same words typed normally, and the recovery the README promises ("the vault still opens with its mnemonic") fails for it.
What happens:
secret initandsecret vault createvalidate the mnemonic withbip39.IsMnemonicValid, which splits on any whitespace, but then derive the key from the raw string:bip39.NewSeedfeeds the bytes as given into PBKDF2. Two spaces between words, or a trailing space or tab (easy inSB_SECRET_MNEMONIC="..."or a pasted line), give a different seed from the single-spaced sentence BIP39 defines, so the vault's key matches no other BIP39 tool's and not the words the user wrote down.Reproduction (
Mis a valid 12-word mnemonic,M2the same words with two spaces after the first word):A second state directory initialised with
Mgets a differentpub.ageat the same derivation index 0.Where:
processMnemonicForVaultpasses the raw string on),DeriveEntropy),NewSeed) againststrings.Fields). The same raw string reachesderiveLongTermKeyFromMnemonicandGetNextDerivationIndex.Correct behaviour: derive from the BIP39 sentence, the words joined by single spaces, NFKD-normalised, wherever the mnemonic is used (
init,vault create,vault import,unlocker add, every unlock); the words as typed with any whitespace then always give the same key.Definition of done
"w1 w2 ... "and from"w1 w2 ..."and requires the same identity; it fails before the fix.Model: fable-5-1