Fix guide · critical · supabase_service_role_in_client

Supabase service_role key in your client bundle

What this rule means

A JWT with "role":"service_role" in its payload was found in your deployed JavaScript. This key bypasses Row-Level Security entirely.

Why it matters

Every visitor to your site can read every row of every table, write to every table, and execute every Postgres function the role has access to. Equivalent to handing the database admin password to anyone with view-source.

How to fix it

  1. Roll the key now. Supabase Dashboard → Settings → API → "Reset service_role". Every consumer using the old key will return 401 — that's correct.
  2. Move the new key to server-only env vars. Use a name *without* the NEXT_PUBLIC_, VITE_, or PUBLIC_ prefix.
  3. Initialize the admin client in server-only code. Concrete patterns per framework:

Next.js (App Router):

// lib/supabase-admin.ts — DO NOT import from client components
import 'server-only';
import { createClient } from '@supabase/supabase-js';
export const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,
  { auth: { persistSession: false, autoRefreshToken: false } }
);

Express:

// db.js
const { createClient } = require('@supabase/supabase-js');
const supabaseAdmin = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_SERVICE_ROLE_KEY
);
module.exports = { supabaseAdmin };

Hono / Cloudflare Workers:

import { createClient } from '@supabase/supabase-js';
app.get('/api/admin', async (c) => {
  const supabaseAdmin = createClient(c.env.SUPABASE_URL, c.env.SUPABASE_SERVICE_ROLE_KEY);
  // ...
});

Supabase Edge Functions:

const supabaseAdmin = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);
  1. Audit Supabase API logs in Dashboard → Logs → API logs for unfamiliar IPs and bulk SELECTs against PII tables during the exposure window.
  2. Add a CI gate. vibecheck https://your-deploy.com --exit-on critical fails the deploy if a service_role JWT lands in the bundle.

Full incident-response post: /blog/supabase-service-role-key-leak

Did vibecheck flag this on your app?

If you reached this page from a vibecheck inspection report, the redacted match in your scan output is the exact string we found in your bundle. After applying the fix above, run the inspection again — the finding should clear.

Run another inspection