Your Supabase Service Key Is Exposed in the Browser — Here's How to Fix It
The Problem
When you build an app with AI coding tools like Lovable, Bolt, or Cursor, they often use Supabase as the database. The problem? These tools frequently put the service_role key in your client-side code instead of only using the safe anon key.
The service_role key bypasses all Row Level Security (RLS) policies. If it's in your browser bundle, anyone can:
- Read every row in every table
- Delete all your data
- Modify other users' records
- Access private files in storage
How It Happens
AI tools see SUPABASE_SERVICE_ROLE_KEY in your .env file and use it wherever they need database access. They don't distinguish between server-side and client-side code.
# .env — the AI tool grabs both
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci... # Safe for browser
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci... # NEVER for browser
The critical mistake is using NEXT_PUBLIC_ prefix on the service key, or importing it in a client component. Next.js bundles any env var prefixed with NEXT_PUBLIC_ into the client JavaScript.
How to Check
Quick check: Open your deployed app, view page source, and search for service_role or your service key value.
Better check: Run a VibeSafe scan — it automatically detects exposed Supabase keys in your client bundle.
Manual check in Next.js:
# Build your app and search the output
next build
grep -r "service_role" .next/static/
If you get any matches, your key is exposed.
How to Fix It
1. Remove NEXT_PUBLIC_ prefix from service key
# WRONG — exposed to browser
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...
# RIGHT — server-only
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...
2. Only use the service key in server-side code
// lib/supabase/server.ts — Route Handlers and Server Components ONLY
import { createClient } from "@supabase/supabase-js";
export function createServiceClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
}
3. Use the anon key for all client-side code
// lib/supabase/client.ts — Browser-safe
import { createBrowserClient } from "@supabase/ssr";
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
4. Rotate your exposed key immediately
If your service key was ever in a client bundle, it's compromised. Go to your Supabase dashboard > Settings > API and rotate the service_role key.
Enable Row Level Security
The anon key is safe only if you have RLS policies on your tables. Without RLS, even the anon key gives full access.
-- Enable RLS on every table
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
-- Only let users read their own data
CREATE POLICY "Users read own data"
ON profiles FOR SELECT
USING (auth.uid() = user_id);
Prevention
- Never prefix secret keys with
NEXT_PUBLIC_ - Use VibeSafe to scan after every deploy
- Add a pre-commit hook that greps for service keys in client files
- Review every AI-generated database call — check whether it runs on the server or client