Skip to content

fix(db): enable RLS on all 11 public app tables and revoke API-role grants#111

Merged
erichare merged 2 commits into
mainfrom
fix/rls-public-tables
Jun 10, 2026
Merged

fix(db): enable RLS on all 11 public app tables and revoke API-role grants#111
erichare merged 2 commits into
mainfrom
fix/rls-public-tables

Conversation

@erichare

Copy link
Copy Markdown
Owner

Summary

Follow-up to #109. Clears the 11 remaining rls_disabled_in_public ERROR lints from the Supabase security advisor: nws_alerts, mrms_files, mrms_grids, mrms_nowcasts, nowcast_verifications, metar_observations, webhook_subscriptions, webhook_deliveries, alert_rules, api_keys, device_tokens.

Three of these are sensitive — api_keys, device_tokens (token column), webhook_subscriptions (secret column) — and all were readable/writable through PostgREST with just the publishable anon key.

Approach

  • Enable RLS with no policies on all 11 tables. Nothing in the stack uses the Supabase Data API (no supabase-js in web/ or sdk-ts/; the FastAPI backend connects via asyncpg as the table-owning postgres role, which bypasses RLS), so this is deny-by-default for the PostgREST API roles and a no-op for the application.
  • Revoke all grants from anon/authenticated as defense in depth, guarded by a DO block checking the roles exist — local dev and CI Postgres have no Supabase roles, so the guard makes the migration portable.
  • Downgrade is symmetric: disables RLS and re-grants when the roles exist.

The migration reaches production via the Railway boot sequence (scripts/railway-start.sh runs alembic upgrade head before starting the API).

Validation

Against an ephemeral postgis/postgis:16-3.5 container:

  • alembic upgrade head with no Supabase roles (the CI/local path): clean, guard skips the REVOKE, RLS enabled on all 11 tables + alembic_version.
  • With anon/authenticated created and granted ALL (the Supabase path): upgrade revokes grants on exactly the 11 app tables; downgrade restores grants and disables RLS; re-upgrade clean.
  • ruff check and ruff format --check pass.

Known limitation

public.spatial_ref_sys also trips the advisor lint but is owned by the PostGIS extension — the postgres role cannot enable RLS on it. Documented in the migration docstring; the longer-term fix is moving the postgis extension out of public.

…rants

Clears the remaining rls_disabled_in_public Supabase advisor lints. All
public application tables were exposed through PostgREST with full
anon/authenticated grants, including api_keys, device_tokens, and
webhook_subscriptions which hold secrets.

Nothing in the stack uses the Supabase Data API (backend connects via
asyncpg as the table owner, which bypasses RLS), so RLS with no policies
is deny-by-default for the API roles and a no-op for the app. The REVOKE
is guarded by a DO block so plain Postgres (local/CI) without Supabase
roles is unaffected.

spatial_ref_sys is PostGIS-owned and cannot have RLS enabled by the
postgres role; documented as a known limitation in the migration
docstring.
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aeroza-web Ready Ready Preview, Comment Jun 10, 2026 3:16pm

Request Review

@erichare erichare merged commit e9937e6 into main Jun 10, 2026
8 of 9 checks passed
@erichare erichare deleted the fix/rls-public-tables branch June 10, 2026 15:21
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