- 
                Notifications
    You must be signed in to change notification settings 
- Fork 502
Description
Describe the bug
Problem summary
When a client sets custom headers via createClient(..., { global: { headers: { 'x-rls': 'true' } } }), those headers are available on normal HTTP requests but are not present on the Realtime WebSocket connection used for Postgres postgres_changes events. If an RLS policy relies on current_setting('request.headers', true) (or otherwise expects that header during Realtime evaluation), the policy evaluates to false and the client receives no realtime messages. Turning RLS off makes the events deliver, confirming this is about missing connection metadata. This appears to be a mismatch between how the SDK sets global.headers (HTTP) and what the Realtime server actually receives during the WS handshake.
Why this is important
Developers commonly use custom headers for tenant identification or extra metadata in multi-tenant setups. RLS policies that depend on such headers work fine for REST requests (via request.headers) but break for Realtime because the WebSocket handshake from browsers doesn't carry arbitrary custom headers. That makes it difficult to implement secure, tenant-aware realtime subscriptions without embedding tenant info in the JWT or using other workarounds. These workarounds are bad since embedding the current tenant in JWT means that it breaks when wanting to use 2 different tenants on 2 different devices.
If websockets dont support headers, Supabase js sdk could just include the header as metadata when setting up the websocket and threat that as the header in RLS.
Library affected
supabase-js
Reproduction
No response
Steps to reproduce
Create following table with rls:
-- create table
create table public.realtime_test (
  id serial primary key,
  content text
);
-- enable RLS
alter table public.realtime_test enable row level security;
-- policy: allow SELECT only if header x-rls is present and equals 'true'
create policy "allow-select-if-x-rls-true" on public.realtime_test
  for select using (
    (current_setting('request.headers', true)::json ->> 'x-rls') = 'true'
  );Have a client listen:
import { createClient } from '@supabase/supabase-js';
const SUPABASE_URL = 'https://your-project.supabase.co';
const SUPABASE_ANON_KEY = '...';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  // developer sets a custom header globally
  global: {
    headers: {
      'x-rls': 'true'
    }
  }
});
// subscribe to postgres changes
const channel = supabase
  .channel('public:realtime_test')
  .on('postgres_changes', { schema: 'public', table: 'realtime_test', event: 'INSERT' }, payload => {
    console.log('received payload', payload);
  })
  .subscribe();Insert a row and observe in the network tab no message appearing inside the WS connection
System Info
System:
    OS: Windows 11 10.0.26100
    CPU: (16) x64 AMD Ryzen 7 7840HS w/ Radeon 780M Graphics
    Memory: 2.89 GB / 15.29 GB
  Binaries:
    Node: 22.18.0 - C:\nvm4w\nodejs\node.EXE
    npm: 11.5.2 - C:\nvm4w\nodejs\npm.CMD
    pnpm: 10.18.2 - C:\nvm4w\nodejs\pnpm.CMD
  Browsers:
    Chrome: 141.0.7390.108
    Edge: Chromium (139.0.3405.102)
  npmPackages:
    @supabase/supabase-js: ^2.75.0 => 2.75.0
    supabase: ^2.51.0 => 2.51.0Used Package Manager
pnpm
Logs
No response
Validations
- Follow our Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Make sure this is a Supabase JS Library issue and not an issue with the Supabase platform. If it's a Supabase platform related bug, it should likely be reported to supabase/supabase instead.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- The provided reproduction is a minimal reproducible example of the bug.