From 384ac708ebcccbc519a22e86bc303c081fae94e9 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 24 May 2026 12:52:55 +0300 Subject: [PATCH] db: remove unnecessary identity cache from `decrypt_cached` Signed-off-by: NotAShelf Change-Id: I9228809562cc9b2f7c0a9d7ece9f5ada6a6a6964 --- src/db/mod.rs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/db/mod.rs b/src/db/mod.rs index ba1c28f..90b3c13 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1218,6 +1218,11 @@ fn load_sensitive_regex() -> Option { /// previously encrypted entries permanently undecryptable, so the permanent /// cache prevents accidental passphrase changes from corrupting the /// clipboard history. +/// +/// Removing the passphrase entirely (disabling encryption) after entries have +/// been stored encrypted also renders those entries permanently unreadable. +/// There is no migration path short of wiping the database. `stash stats` +/// reports affected entries as Undecryptable. #[cfg(feature = "encryption")] fn load_encryption_passphrase() -> Option { use std::process::Command; @@ -1250,25 +1255,18 @@ fn load_encryption_passphrase() -> Option { Some(secret) } -/// Decrypt age-encrypted data using a cached scrypt identity. +/// Decrypt age-encrypted data. +/// +/// `age::scrypt::Identity::new` is cheap since it stores the passphrase only. +/// The scrypt KDF runs inside `age::decrypt` per call, on the per-file salt +/// embedded in the ciphertext header. Caching the Identity would not avoid +/// it. The passphrase itself is cached by [`load_encryption_passphrase`]. #[cfg(feature = "encryption")] fn decrypt_cached(ciphertext: &[u8]) -> Result, StashError> { - static CACHE: OnceLock>> = - OnceLock::new(); - let cache = CACHE.get_or_init(|| Mutex::new(None)); - let mut guard = cache.lock().map_err(|e| { - StashError::Decryption(format!("identity cache lock poisoned: {e}").into()) - })?; - if guard.is_none() { - let passphrase = load_encryption_passphrase().ok_or_else(|| { - StashError::Decryption("no passphrase configured".into()) - })?; - *guard = Some(age::scrypt::Identity::new(passphrase)); - } - let identity = guard - .as_ref() - .ok_or_else(|| StashError::Decryption("identity not available".into()))?; - age::decrypt(identity, ciphertext) + let passphrase = load_encryption_passphrase() + .ok_or_else(|| StashError::Decryption("no passphrase configured".into()))?; + let identity = age::scrypt::Identity::new(passphrase); + age::decrypt(&identity, ciphertext) .map_err(|e| StashError::Decryption(e.to_string().into())) }