Skip to main content

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.

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.messagesis 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).