Is your Supabase anon key safe in an Android app?

Updated

Short answer: the anon key, or the newer publishable key, is meant to be in your app. Whether that is safe depends entirely on Row Level Security. The service_role key, or the newer secret key, must never be in an app at all.

Your APK is readable by anyone who downloads it, so assume every key in it is public. See how to check your APK for leaked keys.

Which Supabase key is which?

Supabase has two kinds of key, each in a current and a legacy form:

  • Publishable key (sb_publishable_...), or the legacy anon key. Supabase describes it as "safe to expose online", including in a "mobile or desktop app". Requests made with it act as the anon role, or as authenticated once the user signs in.
  • Secret key (sb_secret_...), or the legacy service_role key. "Only use in backend components of your app." Supabase lists mobile apps, "where the key is bundled inside the compiled packages", among the places it must never appear.

Supabase refuses secret keys from browsers by checking the User-Agent. An Android app is not a browser, so that check does not protect you: a secret key in an APK works for whoever extracts it.

What actually protects the data?

Row Level Security. With the publishable key, Postgres decides what each request may read or change, table by table, using the policies you write.

Supabase's documentation is blunt about the two states that matter:

  • "A table in an exposed schema without RLS is readable and writable by any role with a grant on it."
  • "Once RLS is enabled, no data is accessible through the API when using a publishable key, until you create policies."

So RLS off means open to anyone with your project URL and the key from your app. RLS on with no policies means closed, which is safe but breaks your app until you add policies.

How do I check every table?

Two ways, and it is worth doing both.

The Security Advisor, in your project's dashboard, runs Supabase's own checks. The one that matters here is rls_disabled_in_public, which it rates as an error: "Anyone with your project URL can read, edit, and delete all data in this table because Row-Level Security is not enabled."

A query in the SQL editor lists every table in the public schema and whether RLS is on:

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

Any row with rowsecurity false is a table your app's key can read and write freely.

What does a good policy look like?

A policy says which rows a role may touch. The pattern Supabase uses in its own examples, letting each signed-in user read only their own profile:

alter table profiles enable row level security;

create policy "Users can view their own profile."
on profiles for select
to authenticated
using ( (select auth.uid()) = user_id );

Write one policy per action you allow (select, insert, update, delete), and allow nothing else.

Which mistakes open the data up anyway?

  • Tables created in SQL. In Postgres, a new table has RLS off until you run alter table ... enable row level security. Migrations and AI-written SQL often leave it out.
  • using (true) for the anon role. Supabase warns that such a policy "grants every unauthenticated visitor read access to every row". Use it only for data meant to be public.
  • Views. A view runs with its owner's rights and ignores the policies on the tables under it, unless it is created with (security_invoker = on). The Security Advisor flags these as security_definer_view.
  • The service_role key in the client to "make it work" when a policy was blocking a request. That turns every policy off for anyone holding your APK.

And if the secret key is already in a build?

Rotate it in the Supabase dashboard first, because every copy of that build still holds the old one. Then move the operation that needed it to your server or an Edge Function, where the secret stays.

This is one of the checks on the Google Play pre-launch checklist. Google's review does not check your database rules, but anyone who downloads your app can.

Sources

Next steps

Still need testers? Get 12 testers for your closed test through a free test-for-test exchange - you test another developer's app, they test yours, and every opt-in is verified.

Applying for production soon? Check your APK against the Google Play launch checklist - target API, permissions that need a declaration, your Data Safety form and leaked keys. The first check is free.

Common questions

Is it safe to put the Supabase anon key in my Android app?
Yes, if Row Level Security is enabled on every table in an exposed schema, with policies that allow only what each user should reach. The key is public by design; RLS is the lock.
What happens if RLS is off on a table?
Supabase says a table in an exposed schema without RLS is readable and writable by any role with a grant on it. Its Security Advisor puts it more plainly: anyone with your project URL can read, edit and delete all the data in it.
I shipped the service_role key. What now?
Treat it as compromised: rotate it in the Supabase dashboard straight away, then move whatever needed it to a server or Edge Function. Old builds still contain the old key.

Apps currently in closed testing

Real developers running the test described above. Test one, and get testers for your own app back.

Closed testing

FitLifeApp is a daily habit and goal-tracking app that helps users monitor water intake, step goals, and mood entries. Please test the following: - Set a daily water goal, add water intake, and check whether the totals and remaining amount update correctly. - Set a step goal and check the progress display. - Add mood entries and review previous records. - Close and reopen the app to check whether your saved data is still available. - Use the app on different days to check daily tracking and history. - Check for confusing navigation, overlapping text, or buttons that do not work correctly. Please join the Google Group and the Google Play closed test using the same Google account. Install the app through Google Play and stay enrolled for at least 14 consecutive days, using its features during the testing period. When reporting a problem, include what happened, the steps to reproduce it, your device model, and Android version. Screenshots are welcome. Thank you for helping improve FitLifeApp!

Health & FitnessProductivityLifestyle

Open to testers · 14-day test

Closed testing

Testers must join the Google Group using the same Google account they use on their Android device's Google Play Store. Testers must open the opt-in link, tap Become a tester, and then download the build via the Play Store link provided.

News & Books

Open to testers · 14-day test

Closed testing

Hi! I’m looking for testers for Crew Bites, a Flutter app that helps groups organize food orders, track who ordered what, and calculate the final bill fairly. Please test the main flow: add people, add food orders, select a restaurant/bundle, add tax/service/tip/delivery, review the total, and save or share the result. I’d especially appreciate feedback about usability, layout, performance, and any bugs on your Android device. Thank you!

Food & Drink

Open to testers · 14-day test

May we use analytics and ad measurement? Analytics (Vercel, and Ahrefs on our public pages) count page views without cookies. Ad measurement lets Google's tag on our public pages see which visits came from our Google ads, using a Google cookie. The site works the same either way. Cookie Policy · How Google uses data ·