> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Realtime Channel Authorization

> Lovable's security scanner periodically flags realtime.messages (used by Supabase Realtime to authorize Channel subscriptions) as having no RLS, allowing any a…

**Status:** Compensating controls in place. Scanner finding `realtime_messages_no_rls` ignored by policy.
**Last Updated:** 2026-04-29

## Why we don't add RLS to `realtime.messages`

Lovable's security scanner periodically flags `realtime.messages` (used by Supabase Realtime to authorize Channel subscriptions) as having no RLS, allowing any authenticated user to subscribe to any channel topic. The literal remediation it proposes — adding RLS policies to `realtime.messages` — **is forbidden by project policy**. Modifying any reserved Supabase schema (`realtime`, `auth`, `storage`, `vault`, `supabase_functions`) can cause service degradation or outages.

See: workspace AGENTS.md (reserved-schema rule) and `mem://constraints/realtime-schema-security-policy`.

## Compensating controls

1. **Table-level RLS on every published table.** All tables exposed to Realtime via `postgres_changes` (`hr_employees`, `pf_messages`, `ce_sms_messages`, `pf_conversations`, `pf_conversation_members`, `gr_contracts`, etc.) have RLS policies that filter by `organization_id`. A subscriber from another org receives zero rows, even if they manage to attach to the topic.
2. **Centralized realtime layer (`@/platform/realtime`).** All client-side Realtime usage is routed through `useRealtimeSubscription`, `useRealtimeBroadcast`, `useRealtimePresence`, `sendBroadcast`, and the `ChannelManager`. Direct `supabase.channel(...)` calls outside this layer are disallowed (PF-66 migration is complete).
3. **Org-scoped topic names for broadcast and presence.** `buildChannelName` automatically prefixes broadcast and presence channel names with `org:{orgId}:` when an org id is available. The hooks (`useRealtimeBroadcast`, `useRealtimePresence`) read the active org from `useCurrentOrganizationId()` and stay disabled until it is known. The standalone `sendBroadcast` utility requires an `organizationId` argument. Result: a user from Org A and a user from Org B subscribing to logical channel `hr_events` are placed on physically distinct topics (`org:A:broadcast:hr_events` vs `org:B:broadcast:hr_events`).
4. **DB channels are unprefixed by design.** `db:` channels rely on `postgres_changes` filters plus table RLS for isolation; renaming them would force a full audit with no security benefit.

## Adding a new broadcast or presence channel

* React: use `useRealtimeBroadcast({ channel: 'my_channel', ... })` or `useRealtimePresence({ channel: 'my_channel', ... })`. The org prefix is applied automatically.
* Non-React: call `sendBroadcast(organizationId, 'my_channel', 'event_name', payload)`. Always thread the active org id through.
* Do not call `supabase.channel(...)` directly. If you have a use case the platform layer doesn't cover, extend the platform layer.

## Scanner finding handling

The Lovable security finding `realtime_messages_no_rls` (scanner: `supabase_lov`) is marked as **ignored** with a reference to this document. If the scanner re-raises it after a future scan, re-apply the ignore — do not attempt to add RLS to `realtime.messages`.

**Re-ignore log:**

* 2026-04-29 — Finding re-raised by `supabase_lov`. Re-ignored; no code or schema changes. Compensating controls verified: org-scoped broadcast/presence topics via `buildChannelName`, table-level RLS on all 28 published tables, centralized realtime layer (PF-66).
