← All posts
·8 min read·GuardLayer

Lovable and the Supabase service role key: keep it out

LovableSupabaseSecretsRLSAI

The Supabase service role key (and its replacement, the sb_secret_ key) is a master key that skips every Row Level Security rule, so it must never be in your Lovable app's frontend code, a VITE_ variable, or a prompt asking the AI to "make it work". Your app only needs the publishable (or anon) key. If you hit a permissions error, fix the RLS policy instead.

If you built your app with Lovable, you may have hit a Supabase error and read a forum reply or an AI answer that said "use the service role key". It does make the error go away. It also hands your whole database to anyone who opens your site.

What is the Supabase service role key?

It is an admin key for your database. Supabase gives every project two kinds of keys:

  • The publishable key (sb_publishable_…), or the older anon key (a long string starting with eyJ). This one is meant to be in your app. Anyone can see it, and that's fine, because your Row Level Security (RLS) policies decide what it can read and write.
  • The secret key (sb_secret_…), or the older service_role key (also an eyJ… string). Supabase's docs say it has the BYPASSRLS attribute, "so it skips every Row Level Security policy you attach."

Think of RLS as the rules on each table, like "users can only see their own orders". The publishable key follows them. The service role key ignores them: whoever holds it can read every row and change or delete any record.

Supabase is deprecating the anon and service_role keys by the end of 2026. The danger is the same with the new ones; our guide to Supabase's publishable and secret keys covers the switch.

Why people end up pasting it into Lovable

The usual story goes like this. You add a feature, the preview breaks, and the error says something like:

  • new row violates row-level security policy for table "profiles"
  • permission denied for table orders

Both mean RLS is on and no policy allows that action. That is RLS working. The quickest-looking fix is a key that ignores RLS, so someone suggests swapping it in. The app works again, and nothing tells you that you just published an admin key.

Where is the service role key safe in a Lovable app?

Only in code that runs on a server. In a Lovable project, that means Edge Functions. Everything in your React frontend runs in the visitor's browser, where anyone can read it.

Lovable's docs are clear about the split:

  • Secrets (in the project toolbar under More → Settings → Secrets) are for backend values. They are "injected into your Edge Functions automatically, and never reach the browser." They are also write-only: once saved, Lovable never shows the value again.
  • VITE_ variables in your .env file are the opposite. Lovable's docs call them "build-time, browser-exposed" values that get embedded into the client bundle. Lovable also asks you to commit .env to your repository, so anything you put there is in your GitHub repo too.

You usually don't need to add the service role key at all. Lovable reserves the SUPABASE_ secret names and fills in SUPABASE_SERVICE_ROLE_KEY automatically, for both Lovable Cloud (the built-in backend, which Lovable says runs on "Supabase's open-source foundation") and a Supabase project you connected yourself. An Edge Function can read it directly:

// supabase/functions/admin-task/index.ts: runs on Supabase's servers, not in the browser
import { createClient } from "npm:@supabase/supabase-js@2";

const admin = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);

Because this code skips RLS, it has to check who is calling before it does anything. Lovable's security guide says the same: code that uses the service role "bypasses RLS, so that code must check permissions itself."

Never put it in any file under src/ (your React app), in a VITE_ variable such as VITE_SUPABASE_SERVICE_ROLE_KEY, or in a chat message asking the AI to use it in the frontend.

If you connected your own Supabase project, its keys live in the Supabase dashboard under Settings > API Keys.

What the mistake looks like

The AI, or a hurried copy-paste, puts the secret key straight into the frontend Supabase client:

import { createClient } from "@supabase/supabase-js";

const SUPABASE_URL = "https://abcdefghijklmnopqrst.supabase.co";
// WRONG: a secret key in frontend code ships to every visitor.
const SUPABASE_KEY = "sb_secret_FAKEkeyFAKEkeyFAKEkey42";

export const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
guardlayer scan · src/lib/supabase.tsLive engine output
Check failed
75/100 · B
  • Criticalsrc/lib/supabase.ts:5

    Hardcoded secret in source

    Move the secret to an environment variable, purge it from git history (e.g. git filter-repo / BFG), and rotate it at the provider.

The VITE_SUPABASE_SERVICE_ROLE_KEY version is just as bad: Vite swaps in the real value when it builds your app. Supabase does reject a secret key sent from a normal browser with HTTP 401, but its docs warn that an attacker "can still use the key from other tools." Treat a secret key in your frontend as leaked. The scan above flags the hardcoded key; it also flags any service role or secret key read through a VITE_ variable, in your code or a committed .env.

How do I check whether my service role key leaked?

Search your code, then search your live site. Two places to look:

1. Your code. In your GitHub repo or Lovable's code editor, search for service_role, SERVICE_ROLE, sb_secret_ and VITE_SUPABASE_SECRET. Hits inside supabase/functions/ are normal. Hits under src/ or in .env are a leak.

2. Your published site. Open it, press F12, go to the Console tab, and paste this. It reads your site's script files and prints the role of every Supabase-style key it finds:

for (const s of document.scripts) if (s.src) fetch(s.src).then(r => r.text()).then(t => {
  for (const k of t.match(/eyJ[\w-]+\.eyJ[\w-]+\.[\w-]+/g) ?? [])
    console.log(JSON.parse(atob(k.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"))).role);
  if (t.includes("sb_secret_")) console.log("sb_secret_ key found in", s.src);
});

anon is expected. service_role, or any sb_secret_ line, means the master key is public. The snippet only reads the scripts loaded on that page, so do the code search too.

What to do if it leaked

  1. Remove it from the frontend first, or the new key leaks the same way. Move the work into an Edge Function and switch the client back to the publishable key.
  2. Rotate it. Follow Supabase's "Rotate a leaked or compromised API key" guide: create a new secret key under Settings > API Keys, move your backend to it, then retire the old one. A leaked secret key can be deleted on its own. A leaked legacy service_role key means deactivating the legacy keys (anon goes too, so switch the frontend to the publishable key) and revoking the legacy JWT secret. If someone is actively using the key, retire it right away.
  3. Check for damage. Look through your tables for rows you didn't create or changes you didn't make, and check your Supabase logs for requests you don't recognize.

Deleting the line doesn't undo a leak: the key is still in your git history and in every published build. On Lovable Cloud, Lovable's Secrets docs only describe a Rotate action for LOVABLE_API_KEY, so ask Lovable support about rotating the database keys. For the deeper Next.js version of this cleanup, see what to do when a Supabase service role key is exposed.

The real fix for the original error

Write the policy that allows the action you need. For a table where each user owns their rows:

alter table public.notes enable row level security;

create policy "Users can add their own notes"
on public.notes for insert to authenticated
with check ((select auth.uid()) = user_id);

create policy "Users can read their own notes"
on public.notes for select to authenticated
using ((select auth.uid()) = user_id);

Also make sure your insert actually sets user_id to the signed-in user, or the check still fails. The full walkthrough is in our guide to "new row violates row-level security policy". Avoid the opposite shortcut too: a policy written as using (true) opens the table to everyone, which is how the apps in the Lovable RLS vulnerability (CVE-2025-48757) leaked.

If you want a second pair of eyes, the free Lovable security check page links two tools: a read-only Live check that shows which tables your public key can read, and a repo scan for projects synced to GitHub that flags secret keys pasted into your code.

FAQ

Is it safe to have the Supabase key in my Lovable frontend? The publishable or anon key, yes. It's designed to be public, and Lovable's own docs list VITE_SUPABASE_PUBLISHABLE_KEY as safe to expose. The service role or secret key, never.

Does Lovable Cloud give me a service role key? Lovable puts SUPABASE_SERVICE_ROLE_KEY in your Edge Function environment automatically, so you never need to handle it. The Secrets view shows names, never values.

Where do I find my Supabase API keys in a Lovable project? With your own Supabase project, under Settings > API Keys in the Supabase dashboard. The publishable key goes in .env as a VITE_ variable; the secret key stays on the server.

The AI told me to use the service role key to fix an RLS error. Should I? No. The error means a policy is missing. Add the right policy, or move the admin action into an Edge Function that checks the caller first.

I pasted my service role key into the Lovable chat. Is that a leak? Lovable's docs say a key pasted into the chat is saved as a project secret, on the backend. The risk is what the AI does next: if the key shows up in src/ or .env, rotate it.

See which of your Supabase tables anyone can read, free

Connect your Supabase project read-only. GuardLayer checks RLS, policies, grants and functions as they are right now, and gives you the SQL to fix each problem. It never reads your rows and can't change anything.

Check my Supabase project

Prefer to check code instead? Scan your repo, no signup.

Keep reading