To secure a Bolt.new + Supabase stack, start by confirming RLS is actually enabled on every table you exported. Then check that client code only ever holds the anon key, that your session survives a page refresh, and that queries filter by user ID even with RLS on. Four checks, and the first one is the one that matters most.
TL;DR
Bolt builds a working full-stack app fast. What it tends to leave behind is the Supabase security config: RLS disabled or written so permissively it may as well be, and sometimes a service_role key sitting in code the browser downloads. Fix those first. Session handling and per-query auth checks come after.
Bolt.new Security Considerations
The split is fairly consistent. Bolt gets the plumbing right and leaves the policy decisions to you:
| What Bolt Does Well | What Needs Review |
|---|---|
| Generates working Supabase client setup | RLS policies may be missing or permissive |
| Creates authentication UI | Session handling may be incomplete |
| Scaffolds CRUD operations | Authorization checks often missing |
| Sets up environment variables | May expose service_role key |
Part 1: Check Supabase RLS Configuration
Verify RLS is Enabled
This is the single highest-value query in the whole blueprint. Run it in the Supabase SQL editor before anything else:
-- Run in Supabase SQL Editor
SELECT
schemaname,
tablename,
rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
-- If rowsecurity is 'false', enable it:
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
Add Proper RLS Policies
-- User profiles: only owner can access
CREATE POLICY "Users can view own profile"
ON profiles FOR SELECT
USING (auth.uid() = id);
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = id);
-- User's items: full access to own data
CREATE POLICY "Users can manage own items"
ON items FOR ALL
USING (auth.uid() = user_id);
-- Public read, authenticated write
CREATE POLICY "Anyone can read posts"
ON posts FOR SELECT
USING (true);
CREATE POLICY "Authenticated users can create posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);
Watch for the permissive default. Bolt commonly writes a policy equivalent to allow read, write: if true, or skips RLS on the table entirely. Both look fine in the dashboard. Neither protects anything.
Part 2: Supabase API Key Security
Check Key Usage in Generated Code
The service_role key bypasses RLS completely, so one copy of it in client code undoes everything in Part 1. Grep for it:
# In your exported project directory
grep -r "service_role" .
grep -r "supabase" . --include="*.ts" --include="*.js"
# Look for hardcoded keys
grep -r "eyJ" . --include="*.ts" --include="*.js"
Correct Key Setup
import { createClient } from '@supabase/supabase-js'
// Only use anon key in client code
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
// NEVER do this in client code:
// const supabase = createClient(url, process.env.SUPABASE_SERVICE_ROLE_KEY)
Part 3: Authentication Flow
Verify Session Handling
The usual symptom is being logged out on every refresh, because the app reads the session once and never subscribes to changes. Here's the shape that works:
import { useEffect, useState } from 'react'
import { supabase } from '../lib/supabase'
import { User, Session } from '@supabase/supabase-js'
export function useAuth() {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
setLoading(false)
})
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null)
}
)
return () => subscription.unsubscribe()
}, [])
return { user, loading }
}
Part 4: Data Fetching Security
Add Authorization to Queries
Generated queries tend to select everything and trust RLS to sort it out. RLS should, but a filter costs you nothing and catches the case where a policy was never written. This is the pattern we see fail most often in exported Bolt apps:
// May fetch all items regardless of owner
const { data } = await supabase
.from('items')
.select('*')
// With proper RLS, this returns only user's items
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
throw new Error('Not authenticated')
}
const { data } = await supabase
.from('items')
.select('*')
.eq('user_id', user.id) // Explicit filter (defense in depth)
Security Checklist
Post-Export Checklist for Bolt + Supabase
RLS enabled on all tables
RLS policies restrict access properly
Only anon key used in client code
Service role key not in repository
Auth state properly persisted
Protected routes check authentication
Environment variables not hardcoded
No sensitive data in git history
Alternative Stacks to Consider
**Bolt.new + Firebase**
If you prefer Firebase's ecosystem
**Bolt.new + Supabase + Vercel**
Add Vercel deployment security
**Bolt.new + Convex**
Real-time alternative with built-in functions
Can I trust Bolt-generated Supabase code?
Trust it to run. Don't trust it to be locked down. The code works, but the security configuration is left to you, so check RLS policies, key usage, and auth flows before anything goes to production.
Why does my Bolt app work without RLS?
Because without RLS the anon key reads and writes everything, which is exactly why the app works. That's convenient while you're building and a full data breach once you're live. Turn RLS on and write the policies before you ship.
How do I add server-side code to a Bolt app?
Export the project, then add API routes or server functions on whatever platform you deploy to. That's the only place the service_role key belongs, and only for genuine admin operations.
Exported a Bolt + Supabase app?
Scan for RLS misconfigurations and key exposure.