Scope unread inbox + dashboard queries to the viewer's enclaves (LC-604) #560
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/lc604-inbox-enclave-scoping"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes LC-604.
db::inbox::list_unreadanddb::chat::list_room_unread_countsfiltered onroom_type = 'public' OR room_members, with no enclave condition. That reads "public" as public to the whole instance - but a room is only public within its enclave, andis_room_accessible(which backsrequire_room_access, and therefore the 403s) requires enclave membership first. The two predicates had drifted, and the list queries were the permissive side.Impact
A user refused a room with HTTP 403 was still shown, on
/inboxand the home dashboard, that room's name, its unread count, a deep link to it, and the body of its newest message.Verified live before the fix. A fresh account (
bob) in no enclave:GET /room/3-> 403general, the previewfiller message 24, a29 unreadpill, andhref="/room/3"/inboxshowed the sameAfter the fix, same account, same rooms:
403unchanged, and both pages show no room link, no room name, and no message body.The fix
Both queries now share
accessible_rooms_sql, one fragment mirroringis_room_accessiblebranch for branch:enclave_idstays unreachable for non-admins, as thereNotably the search query at
chat.rs:1802had already worked this rule out and encoded it inline. The fragment matches it, which is good corroboration that this is the intended semantics and not my invention - and future consumers now inherit it instead of hand-rolling a fourth copy.The admin branch is unchanged in effect: its old form was
room_type = 'public' OR room_type != 'dm' OR member, where the first clause is subsumed by the second.Tests
server/tests/inbox_enclave_scoping.rs. I confirmed the two leak tests fail against the unfixed code and pass with it, so they are real regression coverage rather than decoration:Three further tests guard against over-correcting: an enclave member still sees the public room, enclave membership alone still does not unlock a private channel, and a site admin still sees every channel. Live check agrees -
alice(a member) still sees room 3 and its messages.just checkclean. Full suite unchanged apart from the known pre-existingroutes_drafts::sidebar_shows_draft_pencil_for_room_with_draftfailure.Side effect for LC-603
With visibility correctly scoped, a user in no enclave genuinely has no accessible room, so the LC-372 onboarding empty state renders for them again - confirmed live for
bob. That resolves part of LC-603 as a consequence rather than by separate change.Same class, deliberately not fixed here
Found while auditing; each needs its own decision, and I did not want to bundle behaviour changes into a security fix:
db::chat::list_rooms(chat.rs:151) has the same unscoped predicate. It feeds the forward-message destination picker (forward.rs:42) andapi.rs:98, so a user can see room names from enclaves they are not in. Not a write vector: the forward POST re-validates the destination withis_room_accessible(forward.rs:108) and would reject it. Names only.db::activity.rs(three queries, lines 67/103/139) is also unscoped, but each is keyed to the viewer's own involvement (mentioned, replied to, reacted to), so exposure needs someone with access to act on you deliberately - or for you to have been removed from an enclave after posting. Narrower, still worth closing.I will file these as a follow-up.
🤖 Generated with Claude Code
https://claude.ai/code/session_01P2Lh6DKB15inZTb5z8miu7