Repository navigation
Stop channel members from muting or renaming a channel for everyone #401
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
There are two mute flags:
channel_members.mute_in_app_notificationsis per user. The app's mute button writes this one (apps/webapp/src/components/chatroom/components/ChannelComposer/components/JoinBroadcastChannel.tsx:34). It mutes only that user. This part is correct.channels.mute_in_app_notificationsis per channel. The notification triggers read it first, and return early for all members when it is true.
The app never writes the channel-level flag. But any channel member can write it directly through the Data API, and membership is self-service. So one free account can turn off mentions, replies and other notifications for every member of any channel. The same grant lets a member change the channel name, description and metadata.
- Severity: Medium
- Area: Supabase
- Source: security review of 2026-10-06, finding M3
Production check (2026-10-06, read-only)
authenticatedholds UPDATE onchannelscolumns:allow_emoji_reactions, description, is_avatar_set, last_activity_at, last_message_preview, member_limit, metadata, mute_in_app_notifications, name.- Policy
channels_member_update:USING internal.is_channel_member(id). create_mention_notificationsstarts withselect mute_in_app_notifications into is_channel_muted from public.channels where id = new.channel_id; if not found or is_channel_muted then return new;.create_everyone_notifications,create_regular_message_notifications,create_reply_notificationandcreate_reaction_notificationsread the same channel flag.join_workspaceis executable byauthenticated, andchannel_members_join_insertlets a user add themself to any PUBLIC channel. So anyone can become a member.
Where
- Policy and grant:
packages/supabase/migrations/20260513130000_chat_rls_rollout_catchup.sql:170-183; sourcepackages/supabase/scripts/13-RLS.sql:135-148. - Triggers:
packages/supabase/migrations/20260921120000_chat_mention_token_rule.sql:28-35(and siblings). - Digest emails print the channel name:
packages/email-templates/src/digestWalk.ts:71. The HTML is escaped, so this is spoofed text, not XSS.
Impact
- One update with
mute_in_app_notifications = truestops in-app, push and email notifications for every member of that channel. allow_emoji_reactions = falsedisables reactions for everyone.- A renamed channel shows attacker text in digest emails to every follower.
Fix plan
- Revoke UPDATE on
public.channelsfromauthenticated, and drop thechannels_member_updatepolicy.- Every SQL writer of
channelsis a DEFINER trigger. upsertChannelusesignoreDuplicates(ON CONFLICT DO NOTHING), which needs no UPDATE right.- Confirm with
grep -rn "from('channels')" apps/webapp/srcthat no client code calls.update(onchannels.
- Every SQL writer of
- If a channel-wide mute is a wanted feature, expose it through a DEFINER RPC that checks the document owner.
- Mirror in
packages/supabase/scripts/13-RLS.sql, then runbun run --filter @docs.plus/supabase_back types.
Acceptance criteria
- A signed-in member's
PATCH /rest/v1/channels?id=eq.<id>with{"mute_in_app_notifications": true}is refused. - The per-user mute button still works and mutes only that user.
- Channel creation, first message and unread counts still work.
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] Any channel member can mute notifications for everyone or rename the channel through the API[/-][+]Stop channel members from muting or renaming a channel for everyone[/+]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:
channels_member_updateand runsrevoke update on public.channels from authenticated, anon. The table-level revoke also clears the old column grant.packages/supabase/scripts/13-RLS.sqlmirrors it, andseed.sqlis regenerated. No signature changed, soapps/webapp/src/types/supabase.tsis unchanged.Not done: step 2. No feature needs a channel-wide mute. The column
channels.mute_in_app_notificationsstays, and the notification triggers still read it. OnlySECURITY DEFINERcode and the service role can write it now.Deploy: Supabase SQL has no deploy pipeline, so apply this migration by hand. It also carries #397, #409 and #410.
Check after deploy:
A row in the second query had a channel-wide mute set, most likely through the old grant. Review it before you clear the flag.
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.