All guidesCheck your own repo
On this page

Supabase question

Supabase

How do I check if my Supabase service role key is in client code?

By Ohad Krispin, founder of VibeRaven · LinkedIn

Search the repo for the key in every file that ships to the browser: SUPABASE_SERVICE_ROLE_KEY or an sb_secret_ key under a NEXT_PUBLIC_, VITE_ or other public prefix, a Supabase client created with it in a file that starts with "use client" or sits under src/ in a Vite app, and then search the built bundle for the key value itself. Supabase says a secret key bypasses every Row Level Security policy and must never be used in a browser, so any hit means moving that call behind a server route or Edge Function and rotating the key.

Next step in your repo: run npx -y viberaven@1.6.12 check. It reports a service role or secret key under a NEXT_PUBLIC_, VITE_ or other public prefix in .env.example or vercel.json as service_role_key_in_client_env, and a browser file that creates a Supabase client with that key, or holds an sb_secret_ key or a service_role JWT, as service_role_key_in_client_code.

Paste this to your agent

Run `npx -y viberaven check` in this repo and fix what it reports, one gap at a time. Also check that the service role key is not in client code or in a NEXT_PUBLIC_ or VITE_ variable. Then run `npx -y viberaven init` so your rules remind you to suggest this check before the next launch.

The check runs locally, with no sign-in and no API key. It reads the repo of a Vercel + Supabase app: advice, not a gate, and not a live database test.

Repository check

Check this in your repo

Find the service role key in browser code

For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reads env templates such as .env.example and vercel.json for a service role or secret key under a client prefix (NEXT_PUBLIC_, VITE_, PUBLIC_, EXPO_PUBLIC_, REACT_APP_ or NUXT_PUBLIC_) and reports it as service_role_key_in_client_env. It reads the code files that ship to the browser, such as a file that starts with 'use client' or a file under src/ in a Vite app, and reports a Supabase client created with the service role key, a hardcoded sb_secret_ key or a service_role JWT as service_role_key_in_client_code. When part of the repo cannot be read, it says so as scan_incomplete_service_role instead of giving a clean result.

It does not follow imports, so a shared module with no server-only import that a client component pulls in is not reported unless the key is written in the module itself, and it does not read .env or .env.local values. It is a repository check, not a live database test: it does not open your deployed bundle or tell you whether a key already leaked. It is advice, not a gate, and it exits with code 1 when it finds a blocker.

npx -y viberaven@1.6.12 check

What it printed on a fixture repo

On 2026-10-28 we ran it on a small Next.js fixture repo made for this page, not a customer repo: app/admin/page.tsx starts with "use client" and creates a Supabase client with process.env.SUPABASE_SERVICE_ROLE_KEY, and lib/supabase-admin.ts does the same with no server-only import. Only the client file was reported, which is the import limit above. The output is below, with the folder shortened to ~/app. The finding is a blocker, so the exit code was 1.

$ npx -y viberaven@1.6.12 check

viberaven check · ~/app

🔴 Service role key used in client code  (service_role_key_in_client_code)
   app/admin/page.tsx:3: creates a Supabase client with the service role key. This file ships to the browser, and the service role key skips RLS entirely. Move the call behind a server route or edge function and rotate the key.

Verdict: ❌ 1 blocker, 0 warnings · score 80
Fix: viberaven fix · Details: ~/app/.viberaven/agent-tasklist.md

Search the repo

Two searches find almost every case. The first lists any secret-looking name under a prefix that the framework copies into the browser bundle. The second lists every place the server key is read, so you can check that each one runs only on the server.

# secret-looking names under a public prefix, in env templates, config and code
git grep -nE "(NEXT_PUBLIC|VITE|PUBLIC|EXPO_PUBLIC|REACT_APP)_[A-Z0-9_]*(SERVICE_ROLE|SERVICE_KEY|SECRET)"

# every place the server key is read
git grep -nE "SUPABASE_SERVICE_ROLE_KEY|SUPABASE_SECRET_KEY|sb_secret_"

Which files ship to the browser

In a Next.js app, a file that starts with "use client" and everything it imports is bundled for the browser. A route handler, a server action and a module that starts with import 'server-only' stay on the server; Next.js documents the server-only package for exactly this, so an accidental import from a client component fails the build. In a Vite app, such as a Lovable export, everything under src/ ships to the browser, so the server key belongs only in a Supabase Edge Function, read with Deno.env.get.

Check the built bundle for the key value

A legacy service_role key is a JWT, and its role sits base64-encoded inside it, so searching the bundle for the word service_role finds nothing. Search for the value instead, with the key loaded in your shell from your local env file. The -l flag prints file names only, never the key.

npm run build
grep -rlF "$SUPABASE_SERVICE_ROLE_KEY" .next/static dist 2>/dev/null
grep -rl "sb_secret_" .next/static dist 2>/dev/null

Secret keys are refused in browsers, the legacy key is not

Supabase's API keys docs say requests with an sb_secret_ key from a browser are matched on the User-Agent header and get 401 Unauthorized. They describe that check for secret keys only, so a legacy service_role key in the bundle still works for anyone who copies it. Both bypass row level security.

Checklist

Find it, move it, rotate it

  1. 01Run the two git grep searches above and list every file they print.
  2. 02For each file, confirm it runs only on the server: a route handler, a server action, an Edge Function or a module with import 'server-only'.
  3. 03Rename any service role or secret key that has a NEXT_PUBLIC_ or VITE_ prefix, and read it only in server code.
  4. 04Build the app and search the output for the key value, as shown above.
  5. 05
    If the key was ever in a deployed bundle or a commit, create a new secret key in Settings, API Keys, switch the servers to it, then turn off the old one:
    # list the variables a redeploy must pick up
    vercel env ls production

Related

Sources

Official docs behind this checklist

FAQ

FAQ for builders before launch

Is process.env.SUPABASE_SERVICE_ROLE_KEY in a client component a leak?

Not by itself: Next.js only copies NEXT_PUBLIC_ variables into the browser bundle, so the client gets undefined and the call fails. It is still wrong, because the quick fix people try next is renaming it NEXT_PUBLIC_, which does ship the key. Move the call to a route handler or server action.

Can I keep the service role key in a Vite app if the file is never imported?

Do not rely on that. Vite replaces every VITE_ reference at build time, and anything under src/ can end up in the bundle. Keep the key in a Supabase Edge Function secret instead.