Skip to content

Enable RLS on push_dispatch_queue and host_join_notification_batches - #75

Open
bingb0t5 wants to merge 1 commit into
mainfrom
security/enable-rls-push-dispatch-tables
Open

Enable RLS on push_dispatch_queue and host_join_notification_batches#75
bingb0t5 wants to merge 1 commit into
mainfrom
security/enable-rls-push-dispatch-tables

Conversation

@bingb0t5

Copy link
Copy Markdown
Owner

What

Enables Row-Level Security and revokes the default anon/authenticated grants on the two public tables that were missing it in the I'm In project (qxktbdjzhctfxnafiaxk).

This was already applied to production on 29 Jul 2026 to stop an active exposure. This PR records the change so the repo and the live database agree.

Why

Supabase's security advisor flagged both as rls_disabled_in_public. Supabase grants anon and authenticated full CRUD on public tables by default, so with RLS off anyone holding the public anon key could read and write them over PostgREST.

Confirmed live before the fix: an unauthenticated GET with only the anon key returned rows from push_dispatch_queue (5,542 rows), exposing recipient_user_id, last_error and delivery_trace. anon also held DELETE on the notification queue, so the whole pending push pipeline could have been wiped.

Every other public table in this project already has RLS enabled. These two were introduced without an ENABLE line by:

  • 20260407120000_add_web_push_notifications.sqlpush_dispatch_queue
  • 20260412172000_batch_host_join_notifications.sqlhost_join_notification_batches

Why no policies

Both tables are reached only by the push-dispatch edge function (service_role, which has BYPASSRLS) and by these SECURITY DEFINER functions owned by postgres, verified against the live catalog:

  • enqueue_notification_for_push_dispatch
  • enqueue_host_join_notification_batch
  • flush_host_join_notification_batches
  • invoke_host_join_batch_flush
  • requeue_stale_push_dispatch_jobs
  • get_my_push_diagnostics

Definer functions run as the table owner and are exempt from RLS, so the notification pipeline is unaffected. FORCE ROW LEVEL SECURITY is deliberately not set, which is what preserves that exemption.

Verification (post-apply, against production)

  • Catalog: 0 public tables with RLS disabled.
  • Anon key GET on both tables: 401 permission denied.
  • Service-role GET on push_dispatch_queue: still returns 5,542.
  • FORCE ROW LEVEL SECURITY: 0 tables.

Note on root cause

34 of 36 tables had RLS; these two slipped through because nothing verifies schema end state after a migration merges. A CI check that fails when any public table has RLS off would have caught this in April.

Separately, 12 tables here have RLS on but zero policies (event_private_audience_log 1,351 rows, public_moderation_log_entries 366, others). Those are already deny-by-default so they are not a hole, but worth a look to confirm each is intentional.

🤖 Generated with Claude Code

Supabase's security advisor flagged these two tables as rls_disabled_in_public.
Every other public table in this project already has RLS enabled; these two
were created by 20260407120000_add_web_push_notifications.sql and
20260412172000_batch_host_join_notifications.sql without an ENABLE line.

Since Supabase grants anon and authenticated full CRUD on public tables by
default, RLS being off let anyone with the public anon key read and write them
over PostgREST. Confirmed live before the fix: an unauthenticated GET returned
rows from push_dispatch_queue (5542), which exposes recipient_user_id and
delivery traces, and anon held DELETE on the notification queue.

No policies are added on purpose. Both tables are reached only by the
push-dispatch edge function (service_role, which has BYPASSRLS) and by six
SECURITY DEFINER functions owned by postgres, which run as the table owner and
are exempt from RLS. FORCE ROW LEVEL SECURITY is deliberately not set, which is
what preserves that exemption.

Already applied to production on 29 Jul 2026 to stop the exposure; this
migration records that change so the repo and the live database agree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying im-in with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8056323
Status: ✅  Deploy successful!
Preview URL: https://52aa169c.im-in.pages.dev
Branch Preview URL: https://security-enable-rls-push-dis.im-in.pages.dev

View logs

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant