Finding
Vault AEAD authenticates only the secret bytes. It does not authenticate the fjall key (the credential name), credential type, or a versioned record identity. A writer who can edit the store can therefore transplant a valid serialized entry beneath another name; Vault::get decrypts it successfully and reports the caller-requested name.
Evidence
crates/kryphos/src/crypto.rs:71-111 calls Aead::encrypt(&nonce, plaintext) and decrypt(nonce, encrypted), so both operations use the convenience API with no associated data.
crates/kryphos/src/storage.rs:57-67 keeps credential_type, metadata, status, and history outside the encrypted secret. add encrypts only secret and inserts the serialized value under caller-supplied name at lines 228-264. get fetches by that name, decrypts the stored ciphertext, and constructs DecryptedEntry { name: CompactString::from(name), ... } at lines 277-297. Nothing checks that the ciphertext was created for that record identity.
Related #132 concerns confidentiality of additional columns; this is an authentication and record-substitution defect.
Why this matters
ChaCha20-Poly1305 proves that a ciphertext was produced under the vault key, but the current envelope does not prove which credential the ciphertext belongs to. Store corruption or a write-capable attacker can silently cause a caller asking for one credential to receive another valid secret. The operation succeeds, so downstream use and audit records can attribute the wrong credential without any cryptographic failure.
Desired correction
Define a versioned authenticated envelope and use AEAD associated data that canonically binds at least the vault/entry identity, credential name, credential type, and format version. Verify those fields on every decrypt and provide an explicit migration path for existing entries.
Done when a regression test moves a valid stored value beneath a different key (and separately mutates bound type/version fields) and get rejects it instead of returning a successfully decrypted, misidentified credential.
Finding
Vault AEAD authenticates only the secret bytes. It does not authenticate the fjall key (the credential name), credential type, or a versioned record identity. A writer who can edit the store can therefore transplant a valid serialized entry beneath another name;
Vault::getdecrypts it successfully and reports the caller-requested name.Evidence
crates/kryphos/src/crypto.rs:71-111callsAead::encrypt(&nonce, plaintext)anddecrypt(nonce, encrypted), so both operations use the convenience API with no associated data.crates/kryphos/src/storage.rs:57-67keepscredential_type, metadata, status, and history outside the encrypted secret.addencrypts onlysecretand inserts the serialized value under caller-suppliednameat lines 228-264.getfetches by that name, decrypts the stored ciphertext, and constructsDecryptedEntry { name: CompactString::from(name), ... }at lines 277-297. Nothing checks that the ciphertext was created for that record identity.Related #132 concerns confidentiality of additional columns; this is an authentication and record-substitution defect.
Why this matters
ChaCha20-Poly1305 proves that a ciphertext was produced under the vault key, but the current envelope does not prove which credential the ciphertext belongs to. Store corruption or a write-capable attacker can silently cause a caller asking for one credential to receive another valid secret. The operation succeeds, so downstream use and audit records can attribute the wrong credential without any cryptographic failure.
Desired correction
Define a versioned authenticated envelope and use AEAD associated data that canonically binds at least the vault/entry identity, credential name, credential type, and format version. Verify those fields on every decrypt and provide an explicit migration path for existing entries.
Done when a regression test moves a valid stored value beneath a different key (and separately mutates bound type/version fields) and
getrejects it instead of returning a successfully decrypted, misidentified credential.