How misconfigured PostgreSQL Row Level Security (RLS) and Firebase Security Rules inadvertently expose entire production database tables to public API requests.
In traditional web apps, you write backend code to check if a user is logged in before running an SQL query. In modern platforms like Supabase and Firebase, web browsers talk directly to the database via API. If you create a table and forget to click 'Enable RLS', or write a rule that says `true`, any person on Earth can download your entire database with a single curl command.
An attacker opens browser DevTools on a production app and copies the public Supabase API URL and anon publishable key.
The attacker issues a curl request to /rest/v1/customers?select=* with the anonymous key header.
If RLS was disabled on the customers table, Postgres ignores tenant boundaries and executes a raw table scan.
The complete production table (including hashed passwords, emails, addresses, and private metadata) is returned as a JSON array.
-- VULNERABLE: Table Created Without RLS, Or Flawed Open Policy
CREATE TABLE public.billing_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id),
amount INTEGER NOT NULL,
credit_card_last4 TEXT NOT NULL
);
-- CRITICAL MISTAKE 1: RLS is NOT enabled! Anyone with anon key has full access!
-- CRITICAL MISTAKE 2: Or an insecure policy like:
ALTER TABLE public.billing_records ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow public access" ON public.billing_records
FOR SELECT
USING (true); -- Grants EVERYONE read access!
-- SECURE: Explicit RLS Enforcement & Strict Tenant Ownership Policy
CREATE TABLE public.billing_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id) NOT NULL,
amount INTEGER NOT NULL,
credit_card_last4 TEXT NOT NULL
);
-- 1. Explicitly enable Row Level Security on the table
ALTER TABLE public.billing_records ENABLE ROW LEVEL SECURITY;
-- 2. Force RLS for table owners to avoid accidental superuser bypass
ALTER TABLE public.billing_records FORCE ROW LEVEL SECURITY;
-- 3. Restrict SELECT strictly to verified authenticated user ID
CREATE POLICY "Users can only view their own billing records"
ON public.billing_records
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- 4. Restrict INSERT to ensure user cannot forge someone else's user_id
CREATE POLICY "Users can only insert their own records"
ON public.billing_records
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
ALTER TABLE ... ENABLE ROW LEVEL SECURITY; on every single table in your public schema.TO authenticated on policies intended solely for logged-in users to eliminate anon key exposure.WITH CHECK) on INSERT/UPDATE policies to prevent ID spoofing.[]).