All Posts

Your Supabase Service Key Is Exposed in the Browser — Here's How to Fix It

February 23, 20263 min readVibeSafe Team
supabasesecurityvibe-codinglovablebolt

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

Is your app vulnerable?

Run a free security scan and find out in 60 seconds.

Scan Your App Free