Bolt.new can take you from a prompt to a working, deployed app in an afternoon. What it can't do is know which parts of your app are supposed to be private. That's on you, and it's worth an hour before you share the link.
This checklist is written for the typical Bolt stack: a Vite + React (or similar) frontend, a backend like Supabase, and a host such as Netlify or Bolt's own hosting. Work through it top to bottom.
Understand the shape of a Bolt app
Most Bolt apps are client-side apps. The code runs in your visitor's browser, and the browser talks directly to services like Supabase. That has two consequences:
- Anything in the frontend code, including environment variables, is public.
- Your backend's access rules are the real security boundary, not your UI.
Keep those two facts in mind and most of this checklist makes sense.
☐ 1. Check every VITE_ variable
Vite inlines any variable that starts with VITE_ into your JavaScript bundle at build time. Anyone can read it in their browser's dev tools.
Open your .env file and the Bolt project settings, and list every VITE_ variable. For each, ask: is this meant to be public?
Fine to expose:
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEY(or publishable key), as long as RLS is correctVITE_STRIPE_PUBLISHABLE_KEY(pk_...)
Must never be VITE_:
- Supabase service role or secret key
- Stripe secret key (
sk_...) - OpenAI, Anthropic or any LLM key
- Email provider keys (Resend, SendGrid)
If a secret is in a VITE_ variable, the fix is twofold: rotate the key, and move the code that uses it into a server-side function. See exposed keys in NEXT_PUBLIC_ and VITE_ and how to rotate leaked keys.
☐ 2. Search the code for hardcoded keys
Sometimes keys aren't in env vars at all. They're pasted straight into a file. Download or sync your project to GitHub and search:
grep -rnE "sk_live|sk_test|sk-[A-Za-z0-9]|service_role|sb_secret_" src supabase 2>/dev/null
If your Bolt project is connected to a GitHub repo, also check the history. Our guide on checking whether keys leaked on GitHub shows how with gitleaks.
☐ 3. Turn on RLS for every Supabase table
If your Bolt app uses Supabase, this is the most important item. The anon key in your frontend can query any table in the public schema. Row Level Security decides what it actually gets back.
Run this in the Supabase SQL editor:
select c.relname as table, c.relrowsecurity as rls_on
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'r' and n.nspname = 'public'
order by rls_on;
Any table with rls_on = false is readable and writable by anyone with your anon key. Enable RLS and add owner-based policies. The Supabase RLS checklist walks through the policies step by step.
If you're on Firebase instead, the equivalent check is your Firestore rules. See Firebase security rules for AI-built apps.
☐ 4. Look for "allow everything" policies
When something fails with a permission error, AI tools often "fix" it by adding a policy like:
create policy "Enable read access for all users"
on public.profiles for select using (true);
That makes the error go away by making the table public. Search your migrations and the dashboard for using (true) and with check (true), and replace them with real conditions such as (select auth.uid()) = user_id.
☐ 5. Check that users can't edit privileged fields
If a profiles table has columns like role, is_admin, plan or credits, and users can update their own row, they can upgrade themselves. Try it: sign in, open the browser console, and run an update on your own profile setting plan to 'pro'. If it works, move those fields to a separate table that users can't write to, or revoke update on those columns.
☐ 6. Move secret work into server functions
Anything that needs a secret key has to run on a server. In a Bolt + Supabase app, that usually means a Supabase Edge Function. Store secrets with:
supabase secrets set OPENAI_API_KEY=sk-...
And read them inside the function with Deno.env.get("OPENAI_API_KEY").
Then make sure each function checks who's calling it:
// supabase/functions/generate/index.ts
import { createClient } from "jsr:@supabase/supabase-js@2";
Deno.serve(async (req) => {
const authHeader = req.headers.get("Authorization");
if (!authHeader) return new Response("Unauthorized", { status: 401 });
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!,
{ global: { headers: { Authorization: authHeader } } }
);
const { data: { user } } = await supabase.auth.getUser();
if (!user) return new Response("Unauthorized", { status: 401 });
// ... do the work for this user only
return Response.json({ ok: true });
});
If your project uses Supabase's newer API keys, check the current docs for which env var names your functions receive. Functions that call paid APIs also need rate limits. The ideas in rate limiting OpenAI API routes apply directly.
☐ 7. Don't unlock paid features from the client
A common Bolt pattern: after Stripe Checkout, the user lands on /success, and the frontend sets isPro = true in the database. Anyone can visit /success or run that update themselves.
Paid access should only be granted by a server that has verified a Stripe webhook signature. See Stripe webhook signature verification in Next.js; the same verification works in an Edge Function using stripe.webhooks.constructEventAsync.
☐ 8. Check Storage buckets
If users upload files, check whether the bucket is public. Files in a public bucket can be downloaded by anyone with the URL. For private uploads, use a private bucket with per-user policies and signed URLs. Our Supabase Storage security guide covers the setup.
☐ 9. Review auth settings
In the Supabase dashboard under Authentication:
- Turn on email confirmation if you don't want throwaway signups.
- Set the Site URL and Redirect URLs to your real domain, not a preview URL or
localhost. - Disable sign-in providers you aren't using.
☐ 10. Update dependencies
Generated projects pin whatever versions were current when they were created. Run:
npm audit
npm outdated
Update anything with a known high-severity issue, then re-test your app.
☐ 11. Test as a stranger
Open a private browser window, don't log in, and try to:
- Load pages that should require login.
- Call your Supabase tables directly with the anon key:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*" \
-H "apikey: YOUR_ANON_KEY"
Then log in as one test user and try to read another test user's data. Anything that works here will work for an attacker too.
Before you share the link
If you've ticked every box, you've covered the issues that most often bite apps built this way. For a wider list that covers monitoring and backups too, see the pre-launch security checklist for AI-built apps. Lovable users will find a similar walkthrough in is my Lovable app secure?.
PreflightPR can do a first pass for you. Connect your Bolt project to GitHub, then run a free scan on the public repo to check for exposed keys, tables without RLS, unprotected functions and webhook problems. It's an automated best-effort review, not a pen test, so keep the manual tests above too.