service_role Key Exposure
Auth Safety · AUTH-01 · ADM-17 · Priority: P0
Why It Matters
The Supabase service_role key bypasses all Row Level Security policies. If this key appears in client-side code, any user can extract it from the browser and gain full read/write access to your entire database — including other users' data, billing records, and admin configurations.
This is the single most dangerous secret exposure in the Supabase ecosystem. Unlike the anon key (which is meant to be public), the service_role key is a server-only credential.
Priority: P0 — Immediate remediation required. This is equivalent to publishing your database admin password.
Affected Stack: Supabase, Next.js, any framework using Supabase client
The Problem
AI code generators frequently use the service_role key in client-side Supabase client initialization — because it "just works" (it bypasses RLS, so all queries succeed during development). The key ends up in files that are bundled and shipped to the browser.
Common locations where AI tools place the service_role key:
src/lib/supabase.ts(shared client used by both server and client components)app/layout.tsxorapp/providers.tsx- Environment variables prefixed with
NEXT_PUBLIC_ - Inline in React components
// ❌ What AI tools typically generate
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY! // Exposed to browser!
);
Why This Happens
AI tools optimize for "it works" — and the service_role key makes everything work because it bypasses RLS. The AI doesn't distinguish between server-only and client-safe credentials.
The Fix
Use the anon key for client-side code and the service_role key only in server-side code (API routes, server actions, server components).
// ✅ Client-side: anon key only
import { createBrowserClient } from '@supabase/ssr';
export const supabase = createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // Safe for browser
);
// ✅ Server-side: service_role key (never exposed to browser)
import { createClient } from '@supabase/supabase-js';
export const supabaseAdmin = createClient(
process.env.SUPABASE_URL!, // No NEXT_PUBLIC_ prefix
process.env.SUPABASE_SERVICE_ROLE_KEY! // No NEXT_PUBLIC_ prefix
);
Key rules:
- Never prefix
service_rolekey withNEXT_PUBLIC_ - Never import the admin client in client components
- Use separate client instances for browser vs server
References
- Supabase: API Keys
- Supabase: Server-Side Auth
- OWASP: Security Misconfiguration
- CWE-798: Use of Hard-coded Credentials
ADM-17: Admin Context
The same vulnerability applies in admin contexts — admin dashboards and admin API routes must never use the service_role key in client-side code. Admin operations should use server actions or API routes that keep the service_role key on the server.
Related Checks
- Supabase RLS Safety — AUTH-02, AUTH-03, AUTH-17
- NEXT_PUBLIC_ Secret Exposure — AUTH-05
- Server-Side Auth for Protected Routes — AUTH-04
Is your app safe? Run Free Scan →