FlawOpen
● CWE-862 · OWASP A01:2021

Supabase and Firebase Access Rules (CWE-862): How Missing Row Level Security Exposes Data and How to Fix It

Supabase and Firebase apps let the browser query the database directly with a public key, so the only thing between a visitor and every row is Row Level Security or Security Rules. A table created in SQL without enable row level security, or a Firestore rule left at allow read, write: if true, is readable and writable by anyone. How USING and WITH CHECK policies scope rows to the signed-in user, with a PostgreSQL migration example.

Plain English Explainer (ELI5)

An office building gives every visitor the same lobby key card, printed on the back of the brochure. That is fine as long as each floor's door checks who you are before it opens. One floor was set up in a hurry and its door has no reader at all, so the lobby card opens it and anyone can walk in and read the files. The Supabase anon key is the lobby card: it is meant to be public. Row Level Security is the reader on each floor's door. The fix is to fit a reader to every door and tell it exactly whose files each person may see.

Core Concepts & Subsystem Terms

Public anon key
The Supabase anon key and the Firebase web config ship inside the app bundle, and anyone can copy them. They identify the project, not the user, so they are not a secret and cannot protect data on their own.
Row Level Security (RLS)
A PostgreSQL feature that filters every query through policies. Supabase's Table Editor enables it for new tables, but tables created with SQL or migrations start with RLS off, and the default grants then let anon and authenticated read and change every row.
USING and WITH CHECK
USING decides which existing rows a user can see, update or delete; WITH CHECK decides which new or changed rows they may write. A policy with only USING on insert or update lets users write rows for another organisation.
service_role key
The service_role key bypasses RLS entirely. It belongs only on servers and in edge functions, never in a browser or mobile app.
Firestore test-mode rules
Projects started in test mode get rules that allow all reads and writes until a date, and many are later changed to allow read, write: if true. Rules must check request.auth.uid against the document's owner field.

Step-by-Step Attack Flow

Step 1

The attacker opens the app

The Supabase URL and anon key are in the JavaScript bundle or the mobile app package.

Step 2

They query the table directly

They call the REST endpoint for invoices with only the anon key, skipping the app's own screens. A request against a test project with the anon key and no user session illustrates the case.

Step 3

The database returns every row

RLS is off and anon holds select rights, so PostgreSQL applies no filter and returns every organisation's invoices.

Step 4

Data leaks or is changed

With update and delete granted too, the attacker can also alter or erase records. CVE-2025-48757 was assigned in 2025 after researchers found missing RLS exposing data in more than 170 apps generated by one AI app builder.

Source Code: Flaw vs. Secure Implementation

UNPATCHED FLAW
-- migration.sql: RLS is never enabled on this table
create table public.invoices (
  id bigint generated always as identity primary key,
  org_id uuid not null references public.orgs (id),
  customer text not null,
  amount_cents bigint not null
);

-- the public anon key can read, change and delete every row
grant select, insert, update, delete on public.invoices to anon, authenticated;
HARDENED SECURE PATCH
-- migration.sql: RLS on, anon revoked, one policy per command
create table public.invoices (
  id bigint generated always as identity primary key,
  org_id uuid not null references public.orgs (id),
  customer text not null,
  amount_cents bigint not null
);

-- no policy means no access once RLS is on
alter table public.invoices enable row level security;
revoke all on public.invoices from anon;

-- users see only invoices of organisations they belong to
create policy "members read their org's invoices"
  on public.invoices for select to authenticated
  using (org_id in (select org_id from public.memberships where user_id = (select auth.uid())));

-- new rows must belong to one of the user's organisations; no update or delete policy
create policy "members add invoices to their org"
  on public.invoices for insert to authenticated
  with check (org_id in (select org_id from public.memberships where user_id = (select auth.uid())));

Engineering & System Hardening Checklist

Sources

← Browse the full security directory All vulnerability guides →