Repository navigation
Stop bookmarks and replies from copying a message the user cannot read #410
Description
Activity
- added a parent issue
on Oct 6, 2026 - added 2 commits that reference this issue
on Oct 6, 2026 - changed the title
[-][Security] Bookmarks and reply previews copy any message by id without a channel read check[/-][+]Stop bookmarks and replies from copying a message the user cannot read[/+]on Oct 6, 2026 - addedChatRelated to chat featuresRelated to chat featuresSecuritySecurity, access control, and data exposureSecurity, access control, and data exposure
on Oct 6, 2026 Status: fixed in
335138donclaude/youthful-lovelace-3nvsc6, in migrationpackages/supabase/migrations/20261006130000_close_client_write_and_grant_gaps.sql. Not merged or applied yet.What shipped:
- Step 1.
set_replied_message_previewraises SQLSTATE22023when the parent'schannel_iddiffers fromNEW.channel_id. This channel check also reads a soft-deleted parent, to matchcreate_reply_notification. A parent id that does not exist still gets the "not available" preview. - Step 2.
revoke insert, update, delete on public.message_bookmarks from authenticated, anon.get_user_bookmarksaddsinternal.can_read_channel(m.channel_id). Its signature and return table are unchanged. - Step 3. Mirrored in
packages/supabase/scripts/07-bookmark-functions.sql,10-5-func-replied_msg.sqland13-RLS.sql.seed.sqlis regenerated. No types change was needed.
Left as is:
- The write policies in
packages/supabase/scripts/06-message-bookmarks.sqlstay. Without the grant they have no effect. - Replies already stored across channels are not changed.
- No automated test was added.
Deploy: Supabase SQL has no deploy pipeline, so apply this migration by hand. It also carries #397, #401 and #409.
Check after deploy: a reply in the same channel still shows its preview and notifies the parent's author. Bookmarking through the UI still works. This query returns
false:select has_table_privilege('authenticated', 'public.message_bookmarks', 'INSERT');
Generated by Claude Code
- Step 1.
- added a commit that references this issue
on Oct 9, 2026 Reopened: the board automation closed this issue before the push. The code is on
mainthrough merge083f37d84. It stays open until migration20261006130000_close_client_write_and_grant_gaps.sqlis applied on prod.Deployed 2026-10-09 (run 37906965365, deploy job finished 09:28:41Z). Migration
20261006130000_close_client_write_and_grant_gaps.sqlis applied on prod. Checked read-only:authenticatedhas no INSERT onworkspaces.
Summary
Two DEFINER paths return or copy a message's text using only its id:
message_bookmarksINSERT policy checks onlyuser_id. A user can bookmark anymessage_id.get_user_bookmarks(DEFINER) then returns that message's text with no channel check.set_replied_message_preview(DEFINER) copies the preview of anyreply_to_message_idinto the new message, from any channel. The reply notification trigger also reads the parent by id only. So a reply posted in the user's own channel notifies the author of any message on the platform. The notice goes by push and email, with the replier's chosen display name.Production check (2026-10-06, read-only)
get_user_bookmarksisSECURITY DEFINERand executable byanonandauthenticated. Its body usesauth.uid()but has nocan_read_channelorchannel_memberscheck.message_bookmarkspolicies: INSERTWITH CHECK (auth.uid() = user_id)only.set_replied_message_previewisSECURITY DEFINER.Where
packages/supabase/migrations/20260625150000_bookmark_tab_scoped_list_rpc.sql.packages/supabase/scripts/06-message-bookmarks.sql:57-60. The RPC path checks visibility (packages/supabase/scripts/07-bookmark-functions.sql:36-56); the table path does not.packages/supabase/migrations/20260623120000_chat_media_attachments.sql:496-521, 563.:642-705.Fix plan
set_replied_message_preview, raise an error when the parent message'schannel_iddiffers fromNEW.channel_id. No real UI path replies across channels. This one guard closes the preview copy, themetadata.repliedcopy and the cross-channel reply notification.message_bookmarksfromauthenticatedandanon. The webapp uses only the bookmark RPCs (confirm withgrep -rn "message_bookmarks" apps/webapp/src).get_user_bookmarks, join throughinternal.can_read_channel(m.channel_id)so a bookmark of an unreadable message returns nothing.packages/supabase/scripts/, then runbun run --filter @docs.plus/supabase_back types.Acceptance criteria
POST /rest/v1/message_bookmarksis refused. Bookmarking through the UI still works.get_user_bookmarksdoes not return a message the caller cannot read.Generated by Claude Code