DEV Community

Sora2 Hub Team
Sora2 Hub Team

Posted on

Supabase RLS Checklist for AI-Built Next.js Apps

AI coding agents are good at scaffolding a Next.js app with Supabase Auth and a few tables. They are less reliable at locking those tables down. If Row Level Security (RLS) is missing or too loose, every signed-in user can read everyone else's rows.

Use this checklist before you put a preview or production URL in front of customers.

1. Turn RLS on for every user-facing table

In the Supabase dashboard (or a migration), confirm RLS is enabled on profiles, projects, documents, billing_customer, and any table that stores tenant data. A table with RLS off is public to the anon/authenticated roles that have GRANTs.

2. Prefer auth.uid() policies over client-side filters

Client .eq('user_id', user.id) is not security. The API still returns other rows if a policy allows it. Policies should look like:

  • SELECT / UPDATE / DELETE: auth.uid() = user_id (or auth.uid() = owner_id)
  • INSERT: WITH CHECK (auth.uid() = user_id)

3. Never expose the service role key in the browser

The service role bypasses RLS. Keep it on the server only (Route Handlers, Server Actions, Edge Functions). If an AI agent put SUPABASE_SERVICE_ROLE_KEY in a NEXT_PUBLIC_ env var, rotate the key and remove it from the client bundle.

4. Test with two real accounts

Create user A and user B. As A, create a row. As B, try to fetch it by ID and by list query. Both should fail (empty list / 0 rows / 403). Repeat for update and delete.

5. Watch storage buckets

If you store uploads, bucket policies need the same owner checks. A public bucket with guessable paths leaks files even when table RLS is correct.

6. Avoid SECURITY DEFINER surprises

AI agents sometimes wrap queries in a Postgres function marked SECURITY DEFINER so it can "just work". That function runs as the owner and can bypass RLS unless you re-check auth.uid() inside it. Prefer SECURITY INVOKER (Postgres 15+) or keep RLS-aware SQL in policies, not in privileged helpers.

7. Stripe webhooks and admin jobs

Webhook handlers often need the service role. Scope those writes tightly (update only the customer/subscription row for that stripe_customer_id) and keep the handler server-side.

8. Check GRANTs, not only policies

RLS policies only apply to roles that can touch the table. If anon or authenticated still have broad GRANT ALL, a missing policy fails open for that role. Prefer least privilege: grant SELECT/INSERT/UPDATE/DELETE only where needed, then layer RLS. After AI scaffolding, run \dp public.* (or the dashboard Privileges view) and revoke anything the client roles should not have.

Pre-deploy mini plan

  1. RLS enabled on all tenant tables
  2. Policies use auth.uid() with WITH CHECK on inserts
  3. No service role in client env
  4. Two-user isolation test passed
  5. Storage policies reviewed
  6. No unsafe SECURITY DEFINER helpers bypassing RLS
  7. Webhook path uses service role only on the server
  8. Client-role GRANTs reviewed (least privilege)

Ship when all eight are green.

Where Massvai helps

Massvai is an AI full-stack Next.js app builder. Use it to scaffold faster, then run this checklist yourself before you ship. Agents draft policies; humans verify them.

Top comments (0)