Skip to content

Keys view renders and copies unverified public key metadata while the vault is locked #64

Description

@Sadykhzadeh

Split out of #59, which closes #57 by binding the vault header to its ciphertext. That fix covers everything that touches the vault with the master key — unlock, add, remove, key-auth connect — but it cannot cover the one path that has no key: metadata rendered while the vault is locked. #59 discloses the gap and leaves it open, because closing it is a frontend change.

The gap

list_keys takes no unlocked-vault guard:

// desktop/src-tauri/src/commands.rs:145
pub async fn list_keys(state: State<'_, Arc<AppState>>) -> ApiResult<Vec<KeyMeta>> {
    let vault = state.vault.lock().await;
    Ok(vault.keys_meta().to_vec())
}

keys_meta is read straight off vault.json. After #59 it is authenticated, but authentication is only ever checked by a decrypt, and a locked vault has no key to decrypt with.

The frontend reaches it unconditionally. App.vue:67 calls await keys.load() in onMounted with no vault.unlocked check, and KeyManager.vue then both renders and copies the result while locked:

  • KeyManager.vue:81-83copyPublicKey writes key.public_key_base64 to the clipboard
  • KeyManager.vue:181 — renders public_key_base64 in the public key viewer

Neither is gated on vault.unlocked.

Why it matters

This is #57's headline scenario minus the unlock. An attacker who can write vault.json — the #12 read + write primitive, a restored backup, a synced roaming profile — substitutes one entry's public_key_base64 and fingerprint. The user opens Clavyn on a cold start, does not unlock, opens Keys, clicks copy on the entry sitting under their own label, and pastes the attacker's key into a server's authorized_keys. No decrypt ever ran, so the tag was never checked, and the user was never asked for a passphrase.

The vault does refuse to open the moment anything needs the key, so the window is exactly "locked, and the user only reads". For a value whose entire purpose is to be copied out of the app and pasted into a server, that is the window that counts.

What to do

Gate the Keys view and the clipboard copy on vault.unlocked:

  • do not call keys.load() until the vault is unlocked, or clear the store on lock
  • show an explicit locked / unlock-to-view state in KeyManager.vue rather than an empty list next to a reset button — Bind the vault header to its ciphertext so tampered key metadata cannot pass #59 flags that an empty Keys view on a locked cold start reads as data loss, which is why the metadata was left in the header in the first place
  • refuse copyPublicKey while locked

Adding an unlocked guard to list_keys would enforce this in the core rather than trusting the caller, and is worth doing alongside; the IPC change belongs with the frontend work rather than with #59.

Out of scope for #59 by design: it does not touch desktop/src/**, and says so.

Refs #57, #59.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    SecurityWhen there are security issues

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions