How to Secure Your Supabase Database in a Vibe-Coded App
OverMCP Team
Quick answer
To secure your Supabase database in a vibe-coded app, enable Row Level Security (RLS) on every table, define strict policies that validate user identity and input, and never expose your service_role key client-side. Most AI tools generate Supabase code with RLS disabled or with permissive policies, leaving your data open to unauthorized access.
What to check first
Before you deploy, run through this checklist to catch the most common Supabase database security issues in AI-generated code:
service_role or SUPABASE_SERVICE_ROLE_KEY. This key should never appear in client-side code or public repositories.auth.uid() or auth.role() to restrict access to authenticated users.quote_literal or parameterized queries).SELECT * FROM information_schema.tables to see if any internal tables are accidentally public.Step-by-step fix
Let's fix a common scenario: an AI-generated Next.js app with Supabase that stores user profiles. The AI likely created code like this:
// ❌ Unsafe: no RLS, uses service_role key client-side
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY // 🚨 exposed!
)
// Fetch all profiles – no access control
export async function getProfiles() {
const { data } = await supabase.from('profiles').select('*')
return data
}Step 1: Remove service_role key from client code
Create a server-only client using the @supabase/ssr package or Next.js route handlers:
// lib/supabaseServer.js – server-only
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
// Only use this in API routes or server components
export const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey)For client components, use the anon (public) key with RLS:
// lib/supabaseClient.js
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)
}Step 2: Enable RLS on the profiles table
In Supabase SQL editor:
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;Step 3: Create a policy for authenticated users
-- Allow users to view only their own profile
CREATE POLICY "Users can view own profile"
ON profiles FOR SELECT
USING (auth.uid() = id);
-- Allow users to update only their own profile
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);
-- Allow users to insert their own profile during signup
CREATE POLICY "Users can insert own profile"
ON profiles FOR INSERT
WITH CHECK (auth.uid() = id);Step 4: Update client code to use RLS
// pages/api/profile.js (or app/api/profile/route.ts)
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
export async function GET(request) {
const supabase = createRouteHandlerClient({ cookies })
const { data: { user } } = await supabase.auth.getUser()
if (!user) return new Response('Unauthorized', { status: 401 })
const { data: profile } = await supabase
.from('profiles')
.select('*')
.eq('id', user.id)
.single()
return Response.json(profile)
}Common mistakes
NEXT_PUBLIC_. This key bypasses all RLS – anyone who finds it can read, write, or delete any data.SELECT * FROM profiles without filtering by auth.uid() exposes every user's data..env files. Use a secret leak scanner to catch this before it's too late.quote_literal or parameterized queries.How to prevent Supabase database vulnerabilities in future projects
To avoid these issues from the start, integrate security checks into your workflow:
FAQ
What is the biggest Supabase security mistake in vibe-coded apps?
The biggest mistake is leaving Row Level Security (RLS) disabled on tables that contain user data. AI coding tools often generate code that works without RLS for simplicity, but this means any authenticated user (or anyone with the anon key) can access all records.
How do I find if my Supabase service_role key is exposed?
Search your entire codebase (including Git history) for the key pattern sbp_ or service_role. Also check your .env file and any client-side JavaScript bundles. Use a secret leak scanner to automatically detect exposed keys.
Can I use Supabase RLS policies with Next.js App Router?
Yes. Use the @supabase/ssr package to create a server client that respects the user's session from cookies. Define RLS policies as shown above, and use the anon key client-side – RLS will enforce access control on every query.