Found while reviewing #587. Delegate-WASM change — deliberately not fixed there, because re-keying the delegate to fix a latent handler bug mints another generation, which is the exact failure mode #586 is about. This should land with the next delegate bump that happens for other reasons.
1. handle_delete_request rewrites the key index unconditionally
delegates/chat-delegate/src/handlers.rs::handle_delete_request calls set_key_index even when the key was not in the index. handle_store_request and the CAS-store path both rewrite only when adding a genuinely new key. So a no-op delete still triggers a whole-index rewrite.
2. get_key_index turns an unparseable index into an empty one
…from_reader(...).ok().unwrap_or_default()
An index secret that exists but fails to parse becomes an empty KeyIndex. Combined with (1), one unreadable index read during a delete writes an empty index over the real one: every room key vanishes from ListResponse, the values remain on disk but are unreachable, and there is no error. Silent, total, permanent.
The exposure is not new — handle_store_request uses the same get_key_index when adding a key — but until #587 the client had never sent a DeleteRequest at all, so the delete path has never executed in production.
Suggested fixes
- Make the delete a no-op on the index when the key is not present, matching Store's shape.
- Make
get_key_index distinguish absent (legitimately empty) from corrupt (refuse rather than silently truncate). A corrupt index should fail the request loudly, not erase the user's keys.
Client-side mitigation already in place
#587's branch only sends the DeleteRequest when the marker is believed present, reading the delegate-seeded tri-state (so it works in the deployed app, where localStorage throws). That reduces the unconditional rewrite from once per migrating user to once per genuinely-interrupted migration. It does not address either bug above.
[AI-assisted - Claude]
Found while reviewing #587. Delegate-WASM change — deliberately not fixed there, because re-keying the delegate to fix a latent handler bug mints another generation, which is the exact failure mode #586 is about. This should land with the next delegate bump that happens for other reasons.
1.
handle_delete_requestrewrites the key index unconditionallydelegates/chat-delegate/src/handlers.rs::handle_delete_requestcallsset_key_indexeven when the key was not in the index.handle_store_requestand the CAS-store path both rewrite only when adding a genuinely new key. So a no-op delete still triggers a whole-index rewrite.2.
get_key_indexturns an unparseable index into an empty oneAn index secret that exists but fails to parse becomes an empty
KeyIndex. Combined with (1), one unreadable index read during a delete writes an empty index over the real one: every room key vanishes fromListResponse, the values remain on disk but are unreachable, and there is no error. Silent, total, permanent.The exposure is not new —
handle_store_requestuses the sameget_key_indexwhen adding a key — but until #587 the client had never sent aDeleteRequestat all, so the delete path has never executed in production.Suggested fixes
get_key_indexdistinguish absent (legitimately empty) from corrupt (refuse rather than silently truncate). A corrupt index should fail the request loudly, not erase the user's keys.Client-side mitigation already in place
#587's branch only sends the
DeleteRequestwhen the marker is believed present, reading the delegate-seeded tri-state (so it works in the deployed app, where localStorage throws). That reduces the unconditional rewrite from once per migrating user to once per genuinely-interrupted migration. It does not address either bug above.[AI-assisted - Claude]