# 58 of 154 public Supabase apps had a working service_role key in git

> Source: <https://dev.to/cekuu35/58-of-154-public-supabase-apps-had-a-working-servicerole-key-in-git-gie>
> Published: 2026-09-24 09:12:40+00:00

If you build with Lovable, Bolt, Cursor, or any AI coding tool on top of Supabase, this week's scan has a number you should see: **58 of 154 public apps shipped a working `service_role` key inside their git history.**

That key is the master key for the database. It bypasses Row-Level Security entirely — the same RLS you turned on in the dashboard does not apply to it. Anyone who finds it can run `DELETE FROM "user"` over the REST API and the database believes it.

I did not read a single row of anyone's data. I verified the key decodes to a real project (`iss`, `ref`, and a `service_role` role claim), checked that the project is live, and moved on. That is enough to know the exposure is real.

Three causes, in the order I actually saw them:

**AI tools scaffold `.env` files with real keys.** Your AI agent opens the Supabase dashboard to "wire up the database," copies the project URL and keys into `.env`, and commits it because the app "won't build" otherwise. The placeholders in memory get replaced by real values — then `.gitignore` is never created for `.env`.

**The file that ships is named like a dev file.** `.env.txt`, `env.local`, `.env.development` — these often bypass the `.gitignore` pattern `*.env` because the name is subtly different.

**History keeps the key after the file is deleted.** Deleting `.env` in a new commit is not a fix. The key stays in every previous commit unless you rewrite history.

Paste this into supabase.com/dashboard → your project → SQL Editor. It shows every object a `service_role` (or anon) key can reach that your RLS policies do not actually protect:

```
select n.nspname, c.relname, c.relrowsecurity
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind in ('r', 'p')
  and n.nspname = 'public'
  and not c.relrowsecurity
order by n.nspname, c.relname;
```

Every row of that result is readable and writable by anyone holding the public anon key that ships in your browser bundle. If `relrowsecurity` is false, Row-Level Security is not even on for that table.

For git: `git log --all --oneline -- .env` shows every commit the file touched. If you see more than the one "deleting it" commit, the key is still in history.

`git filter-repo` or BFG).`.env`, create `.gitignore` entry `*.env*` before first launch."
Every check in that list — RLS coverage, open policies, `USING (true)` traps, write-without-read policy conflicts, and the git-history secret scan — I've packaged as a free, read-only, 60-point pre-launch check that runs against your own DB in about 30 seconds and uploads nothing:

**github.com/cekuu35/supabase-rls-leak-demo**

The scan behind this post ran the same evidence standard: decode the JWT, confirm `role == "service_role"`, confirm the project is live. No data was read, no exports were made, nothing was saved. If you want a second pair of eyes on your schema after rotating — policy conflicts, unisolated joins, service_role exposure paths — that is the read-only review I do as paid work, and the email + Gumroad link are in my profile.
