Stack · Security

Supabase security checklist for AI-generated apps

Supabase is a favorite of AI builders because it gives you a Postgres database, auth, and storage behind one client SDK. The catch is that Supabase exposes your database directly to the client, and the only thing standing between a user and everyone else’s data is Row Level Security - which generated apps frequently ship without, or with policies that do not actually restrict access.

This checklist covers the Supabase-specific mistakes that recur in AI-built apps: RLS, policy correctness, key handling, and storage.

45%

of AI-generated code ships with a known security weakness (Wiz · Databricks)

+107%

rise in vulnerabilities per codebase year over year (Checkmarx)

Row Level Security is not optional

Because the Supabase client talks to your database directly, a table without RLS enabled is readable and writable by anyone with your public anon key - which ships in the browser. This is the single most damaging and most common Supabase mistake in generated apps.

  • Enable RLS on every table

    A table with RLS off is fully exposed through the public API. There is no "private by default".

  • Write policies that check ownership

    A policy must compare the row to auth.uid(), not merely allow any authenticated request. "Authenticated" is not "authorized".

  • Cover all operations

    Separate policies govern select, insert, update, and delete - confirm each is restricted, not just select.

Keys, service role, and storage

Supabase has two keys, and confusing them is dangerous. The anon key is public by design; the service role key bypasses RLS entirely and must never reach the client.

  • Never expose the service role key

    It ignores RLS. It belongs only in trusted server-side code, never in the browser bundle or client env.

  • Secure storage buckets

    Storage has its own access policies. Confirm buckets are not public unless intended, and enforce per-path ownership.

  • Validate writes in policies

    Use policy checks to constrain what a user can insert or update, not just which rows they can see.

Scale and cost on Postgres

Supabase is Postgres, so the usual database scale rules apply: index the columns you filter and sort on, paginate large reads, and avoid query patterns that fan out per row. Generated apps often skip indexes, which is invisible until the table grows.

The pre-launch checklist

  • Enable RLS on every table

    No table should be reachable through the public API without it.

  • Write ownership-based policies, not "is authenticated"

    Compare rows to auth.uid().

  • Add policies for insert/update/delete, not just select

    Restrict every operation.

  • Keep the service role key server-side only

    It bypasses RLS - never ship it to the client.

  • Lock down storage buckets and paths

    Per-path ownership; no accidental public buckets.

  • Index filter/sort columns and paginate reads

    Keep Postgres fast as data grows.

Run this checklist on your repo, automatically

PeakStack scores every commit for security, scalability, and cost - with the exact line and a fix.

Request access

FAQ

Why are AI-generated Supabase apps often insecure?

Because Supabase exposes the database to the client, and security depends entirely on Row Level Security. Generated apps frequently ship with RLS disabled or with policies that only check authentication - leaving every row readable or writable by any user.

Is the Supabase anon key safe to expose?

Yes - the anon key is public by design and ships in the browser. Your security comes from RLS, not from hiding it. The service role key is the dangerous one: it bypasses RLS and must stay server-side only.

How do I verify my Supabase policies are correct?

Confirm RLS is enabled on every table and that each policy checks ownership (auth.uid()) for select, insert, update, and delete. PeakStack flags missing RLS and permissive policies on every commit.

Related guides