Repository navigation
Stop bookmarks and replies from copying a message the user cannot read #410
Copy link
Copy link
Closed
Labels
ChatRelated to chat featuresRelated to chat featuresSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Milestone
Description
Summary
Two DEFINER paths return or copy a message's text using only its id:
- Bookmarks. The
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. - Reply previews. The BEFORE trigger
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.
- Severity: Low for the read paths today, because every channel is PUBLIC. It becomes Medium once Private chat is gated (see the Private chat issue): these paths would bypass that gate. Today, anyone can use the cross-channel reply notification to send spam and phishing.
- Area: Supabase
- Source: security review of 2026-10-06
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
- Bookmark RPC:
packages/supabase/migrations/20260625150000_bookmark_tab_scoped_list_rpc.sql. - Bookmark table policy:
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. - Reply preview trigger:
packages/supabase/migrations/20260623120000_chat_media_attachments.sql:496-521, 563. - Reply notification trigger: same file
:642-705.
Fix plan
- One reply guard. In
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. - Bookmarks.
- Revoke INSERT, UPDATE and DELETE on
message_bookmarksfromauthenticatedandanon. The webapp uses only the bookmark RPCs (confirm withgrep -rn "message_bookmarks" apps/webapp/src). - In
get_user_bookmarks, join throughinternal.can_read_channel(m.channel_id)so a bookmark of an unreadable message returns nothing.
- Revoke INSERT, UPDATE and DELETE on
- Mirror in
packages/supabase/scripts/, then runbun run --filter @docs.plus/supabase_back types.
Acceptance criteria
- A reply whose parent is in another channel is refused.
- A normal reply in the same channel still shows its preview and notifies the parent author.
- A direct
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
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
ChatRelated to chat featuresRelated to chat featuresSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Activity
fix(supabase): close client write and function grant gaps
docs: record the security and push fixes in changelogs and agent docs
[-][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[/+]HMarzban commentedon 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:
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.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.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:
packages/supabase/scripts/06-message-bookmarks.sqlstay. Without the grant they have no effect.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:Generated by Claude Code
chore(merge): land the security and push fixes branch
HMarzban commentedon 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.HMarzban commentedon Oct 9, 2026
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.