File: crates/client/src/lib.rs:993
Severity: security
Obvious? no
load_identity() calls storage::load_identity_bytes() which on wasm32 (crates/client/src/storage.rs:269) does web_sys::window()?.local_storage().ok()??.get_item(...). Every error path — Safari private mode (local_storage() throws), Firefox "block all storage", dom.storage.enabled=false, QuotaExceededError from a previous corrupted/full state, base64 decode failure — collapses into None. The caller treats None indistinguishably from "first run" and immediately generates a brand-new Identity::generate(), overwrites whatever was there with save_identity_bytes, and proceeds.
Threats: (a) a user whose storage transiently fails (extension toggle, profile cleanup, quota pressure from an unrelated app on the same origin) loses their long-term Ed25519 identity and reappears as an unauthenticated stranger to every server they own — including the owner of a server, who now cannot make admin events because the DAG only trusts the original key; (b) tampering attack: an extension or co-tenant page that clears just willow_identity forces fresh generation, then can race the save and inject an attacker-controlled key.
Fix: distinguish Err / quota / disabled-storage from Ok(None). Surface read errors to the UI ("Browser storage unavailable — your identity cannot be loaded; continue ephemeral?") and never overwrite a key slot if the read failed for a reason other than "empty".
Filed by /general-audit @ 88498a5 (2026-05-04). master: #600.
File:
crates/client/src/lib.rs:993Severity: security
Obvious? no
load_identity()callsstorage::load_identity_bytes()which on wasm32 (crates/client/src/storage.rs:269) doesweb_sys::window()?.local_storage().ok()??.get_item(...). Every error path — Safari private mode (local_storage()throws), Firefox "block all storage",dom.storage.enabled=false,QuotaExceededErrorfrom a previous corrupted/full state, base64 decode failure — collapses intoNone. The caller treatsNoneindistinguishably from "first run" and immediately generates a brand-newIdentity::generate(), overwrites whatever was there withsave_identity_bytes, and proceeds.Threats: (a) a user whose storage transiently fails (extension toggle, profile cleanup, quota pressure from an unrelated app on the same origin) loses their long-term Ed25519 identity and reappears as an unauthenticated stranger to every server they own — including the owner of a server, who now cannot make admin events because the DAG only trusts the original key; (b) tampering attack: an extension or co-tenant page that clears just
willow_identityforces fresh generation, then can race the save and inject an attacker-controlled key.Fix: distinguish
Err/ quota / disabled-storage fromOk(None). Surface read errors to the UI ("Browser storage unavailable — your identity cannot be loaded; continue ephemeral?") and never overwrite a key slot if the read failed for a reason other than "empty".Filed by
/general-audit@88498a5(2026-05-04). master: #600.