fix(security): enforce room access on reaction handlers (LC-149) #182
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/lc-149-reactions-authz"
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?
What
Closes the reaction-handler authorization gap from the LC-148 audit (findings S1-S3).
routes/reactions.rswas the only id-based handler that did not re-check room access the waypinned/bookmarks/polls/roomdo.Fixes
toggle_reaction: fetched a message by attacker-supplied id, toggled a reaction, broadcastReactionAdded/Removedinto the room, and fired thereaction.addedoutgoing webhook, all with no access check. Any authenticated user could react in a private room / DM / enclave they cannot see, inject a live WS fragment into that room, and probe message existence. Now gated byrequire_room_access.get_picker: had no auth extractor at all; leaked a room's custom-emoji inventory and was an unauthenticated message-existence oracle. Now requiresAuthUser+ access check, and 404s the message before the access check so a non-member cannot distinguish "no such message" from "no access".{emoji}path segment was inserted unbounded/unvalidated. Now bounded byis_valid_reaction(non-empty, <= 64 bytes, no control chars).require_room_accessmirrorspinned.rs(admin-awareis_room_accessible).Test
New
routes_reactions_authzintegration test: non-member gets 403 toggling and opening the picker on a private-room message; a General member gets 200 for both; an oversized emoji token gets 400.just check,just test,just test-saasgreen (theroutes_uploadsupload-pipeline flake is pre-existing and passes in isolation).Part of the LC-159 post-audit story.
🤖 Generated with Claude Code
reactions.rs was the one id-based handler that did not re-check room access, unlike pinned/bookmarks/polls/room. Three gaps (audit S1-S3): - toggle_reaction (high, IDOR): fetched a message by attacker-supplied id and toggled a reaction + broadcast ReactionAdded/Removed + fired the reaction.added outgoing webhook with no access check. Any authenticated user could react in private rooms / DMs / enclaves they cannot see, inject a live WS fragment into that room, and probe message existence. - get_picker (med): had no auth extractor at all; leaked a room's custom-emoji inventory and served as an unauthenticated message-existence oracle. - {emoji} path segment (med): inserted into message_reactions unbounded and unvalidated. Adds a require_room_access helper (admin-aware is_room_accessible, mirroring pinned.rs) called by both handlers, an AuthUser extractor on get_picker, and an is_valid_reaction bound (non-empty, <= 64 bytes, no control chars) before the toggle. get_picker now 404s the message before the access check so a non-member cannot distinguish "no such message" from "no access". New routes_reactions_authz integration test: a non-member gets 403 toggling and opening the picker on a private-room message; a General member gets 200 for both; an oversized emoji token gets 400. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>