Answers · Supabase

Is my Supabase anon key safe to expose?

Yes, as long as Row Level Security is enabled on every table the key can reach and your policies only allow what an anonymous visitor should see. The anon key (Supabase's newer name for it is the publishable key, sb_publishable_…) is designed to be public: it sits in your browser bundle and anyone can read it from the network tab. It is not a password. Row Level Security is.

The key that must never be exposed is the service_role key (now the secret key, sb_secret_…). It bypasses Row Level Security entirely.

What the anon key actually grants

Every request with the anon or publishable key runs as the Postgres role anon, or authenticated once a user signs in through Supabase Auth. Your Row Level Security policies are written against those two roles. If a table has RLS off, the key gets whatever that table's grants allow, with no policy in the way.

Supabase is deprecating the legacy anon and service_role JWT keys by the end of 2026 in favour of publishable and secret keys. The rules above are the same for both.

When "safe to expose" stops being true

  • A table in an exposed schema with Row Level Security disabled.
  • A policy that matches everyone, such as using (true) on a table holding other people's data.
  • An update policy that lets users write columns they shouldn't, such as their own plan or role.
  • The service_role or secret key in client code, usually via a framework prefix like NEXT_PUBLIC_ or VITE_ that tells the bundler to ship it.

Four checks to run

  1. List tables without RLS. In the SQL editor:

    select tablename, rowsecurity
    from pg_tables
    where schemaname = 'public';

    Any false is a table the anon key can reach without a policy in the way.

  2. Ask the API what a stranger gets. Use only the public key, no user session, on a project you own:

    curl "https://<project-ref>.supabase.co/rest/v1/profiles?select=*" \
      -H "apikey: $SUPABASE_ANON_KEY"

    For a table that holds user data, the answer should be [] or a permission error. Rows mean anyone with your site open can read them. (Older examples also send a legacy anon key as Authorization: Bearer; the result is the same: what the internet sees.)

  3. Make sure the secret key isn't in what you ship. After a production build:

    grep -rlE "sb_secret_|service_role" .next/static dist build 2>/dev/null

    Any file listed here is served to browsers. Rotate the key, then move it to server-only code under a name without a public prefix.

  4. Open the Security Advisor in the Supabase dashboard. It flags common problems with the built-in roles, including tables without RLS. Read each finding before dismissing it.

Ship Check Kit · $29

Rather not run it by hand?

The Ship Check Kit runs these as part of 44 launch checks your coding agent works through, including SC-RLS-02 (a live probe of what the public key can read) and SC-RLS-04 (whether users can edit privileged columns such as their own plan), with the evidence and the fix for each failure.

Checked against Supabase: API keys, Supabase: Row Level Security on 2026-10-23. Only test apps and accounts you own or are allowed to test.