Skip to content

Stop channel members from muting or renaming a channel for everyone #401

Closed

Description

@HMarzban

Summary

There are two mute flags:

  • channel_members.mute_in_app_notifications is 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_notifications is 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)

  • authenticated holds UPDATE on channels columns: 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_notifications starts with select 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_notification and create_reaction_notifications read the same channel flag.
  • join_workspace is executable by authenticated, and channel_members_join_insert lets 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; source packages/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 = true stops in-app, push and email notifications for every member of that channel.
  • allow_emoji_reactions = false disables reactions for everyone.
  • A renamed channel shows attacker text in digest emails to every follower.

Fix plan

  1. Revoke UPDATE on public.channels from authenticated, and drop the channels_member_update policy.
    • Every SQL writer of channels is a DEFINER trigger.
    • upsertChannel uses ignoreDuplicates (ON CONFLICT DO NOTHING), which needs no UPDATE right.
    • Confirm with grep -rn "from('channels')" apps/webapp/src that no client code calls .update( on channels.
  2. If a channel-wide mute is a wanted feature, expose it through a DEFINER RPC that checks the document owner.
  3. Mirror in packages/supabase/scripts/13-RLS.sql, then run bun 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

Activity

  1. added theissue type on Oct 6, 2026
  2. changed the title [-][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[/+] on Oct 6, 2026
  3. added
    ChatRelated to chat features
    SecuritySecurity, access control, and data exposure
    on Oct 6, 2026
  4. HMarzban commented on Oct 6, 2026

    @HMarzban
    CollaboratorAuthor

    Status: fixed in 335138d on claude/youthful-lovelace-3nvsc6, in migration packages/supabase/migrations/20261006130000_close_client_write_and_grant_gaps.sql. Not merged or applied yet.

    What shipped:

    • Step 1. The migration drops channels_member_update and runs revoke update on public.channels from authenticated, anon. The table-level revoke also clears the old column grant.
    • Step 3. packages/supabase/scripts/13-RLS.sql mirrors it, and seed.sql is regenerated. No signature changed, so apps/webapp/src/types/supabase.ts is unchanged.

    Not done: step 2. No feature needs a channel-wide mute. The column channels.mute_in_app_notifications stays, and the notification triggers still read it. Only SECURITY DEFINER code 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:

    select has_any_column_privilege('authenticated', 'public.channels', 'UPDATE');  -- expect false
    select id, name from public.channels where mute_in_app_notifications;            -- expect no rows

    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

  5. HMarzban commented on Oct 9, 2026

    @HMarzban
    CollaboratorAuthor

    Reopened: the board automation closed this issue before the push. The code is on main through merge 083f37d84. It stays open until migration 20261006130000_close_client_write_and_grant_gaps.sql is applied on prod.

  6. HMarzban commented on Oct 9, 2026

    @HMarzban
    CollaboratorAuthor

    Deployed 2026-10-09 (run 37906965365, deploy job finished 09:28:41Z). Migration 20261006130000_close_client_write_and_grant_gaps.sql is applied on prod. Checked read-only: authenticated has no INSERT on workspaces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ChatRelated to chat featuresSecuritySecurity, access control, and data exposurebugSomething isn't working

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions