Skip to content

False positives in supabase-rls-policy-risk: cross-migration DROP/REPLACE, service_role scoping, IF EXISTS cleanup #910

Description

@jaypohpohpoh

Summary

react-doctor/supabase-rls-policy-risk produces false positives on three common, safe Supabase RLS patterns because it lints each migration file in isolation and pattern-matches policy text without resolving the final schema state. We hit all three on a real Next.js + Supabase app (v0.5.7); after independent review each flagged line was confirmed safe. Filing because the rule's value is high and these would be cheap to fix.

The three false-positive classes

1. Cross-migration DROP / REPLACE not followed. A broad for all policy created in migration A is dropped and recreated as for insert-only in a later migration B. The rule flags the original for all in A even though that policy no longer exists in the live schema.

-- migration A (flagged)
create policy "p" on t for all using (is_admin()) with check (is_admin());
-- migration B (the rule never sees this when linting A)
drop policy if exists "p" on t;
create policy "p_insert" on t for insert with check (is_admin());

2. to service_role read as a "bypass" when it is a tightening. Scoping an INSERT policy to service_role removes anon/authenticated write access — it's the fix, not the risk. service_role is server-only and never shipped to the browser, so the only role the policy admits is unreachable by an attacker.

-- flagged as "references a service-role bypass" — but this is the hardening commit
create policy "system_insert" on audit_log for insert to service_role with check (true);

3. ALTER TABLE IF EXISTS ... DISABLE ROW LEVEL SECURITY on an already-dropped table. A cleanup migration that tears down policies for tables dropped earlier is flagged as "disables RLS," but IF EXISTS makes it a no-op at apply time (the table doesn't exist).

-- pure cleanup migration; table was dropped in an earlier migration
alter table if exists public.old_table disable row level security; -- flagged, but no-op

Suggested fixes (any one helps)

  1. Make the rule migration-folder-aware: resolve the final policy state across the migration set before flagging (so a dropped/replaced policy isn't reported).
  2. Treat a policy scoped to service_role (or any non-public, non-anon, non-authenticated role) as not-publicly-writable, rather than as a bypass.
  3. Recognize IF EXISTS guards / dropped-table cleanup so DISABLE ROW LEVEL SECURITY on a non-existent table isn't flagged.

Workaround

Suppressing per-file via ignore.overrides in doctor.config.ts. Happy to provide more detail if useful. Thanks for the tool.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions