ShipReady Guide · Database · Lovable · Bolt · Supabase

Is My Lovable App Secure? A 10-Minute Check Before You Share It

Lovable and many Bolt apps pair a React front end with a Supabase backend. That stack is secure when it's set up right, but the builder optimizes for "it works," not "strangers can't read it." Here are the six checks we'd run before sharing the link.

8-minute readUpdated Oct 2026All guides

The short version

  • Tables: row-level security on, with policies, tested using the public key.
  • Keys: only the anon or publishable key in the front end. Secrets live in Edge Function secrets.
  • Edge Functions: check who's calling before doing anything paid or private.
  • Storage and auth: private buckets by default, and redirect URLs limited to your domains.

1. How these apps are put together

Your front end is a React app that runs entirely in the browser. It talks to Supabase directly using the project URL and a public key, both of which are visible to every visitor. That's normal and by design.

So the question isn't whether someone can find your Supabase URL and key. They can. The question is what that key lets them do, and the answer is decided by your database policies, not your UI.

2. Check every table has row-level security

In the Supabase dashboard, open the Policies page. Every table in the public schema should show RLS enabled with at least one policy. Supabase's Security Advisor also flags tables without RLS.

Then test it the way an outsider would, using only your public key:

Try to read a table with only the public key
curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=5" \
  -H "apikey: YOUR_ANON_OR_PUBLISHABLE_KEY"

If that returns other users' rows, anyone can do the same. Turn on RLS and add policies with the RLS guide. Repeat for each table that holds anything private.

3. Make sure no secret keys reached the front end

Search your project for service_role, sb_secret_, sk- and sk_live_. None of them belong in front-end code or in a VITE_ variable. See which keys are safe in the browser.

Calls to OpenAI, Stripe or email providers belong in a Supabase Edge Function, with the key stored as a Supabase secret. If a secret was ever in the front end, rotate it.

4. Check your Edge Functions

An Edge Function's URL is public. Before it does anything private or paid, it should confirm who's calling:

supabase/functions/chat/index.ts
import { createClient } from "npm:@supabase/supabase-js@2";

Deno.serve(async (req) => {
  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_ANON_KEY")!,
    { global: { headers: { Authorization: req.headers.get("Authorization") ?? "" } } },
  );

  const { data: { user } } = await supabase.auth.getUser();
  if (!user) return new Response("Unauthorized", { status: 401 });

  // ...only now call OpenAI, Stripe, etc. with keys from Deno.env
  return new Response("ok");
});

For anything that calls an AI model, add a per-user limit too. See rate limiting AI routes.

5. Check storage buckets

In Storage, look at each bucket. A public bucket serves every file to anyone with the URL, which is right for logos and public images and wrong for invoices, uploads or profile documents.

Make private buckets the default, and add policies on storage.objects so users can only read and write their own folder.

6. Check auth settings and admin pages

  • Redirect URLs: in Authentication settings, the Site URL and allowed redirect URLs should list only your real domains, not wildcards you added while testing.
  • Email confirmation: decide on purpose whether new accounts must confirm their email.
  • Admin pages: hiding an admin button in the UI doesn't protect anything. Admin-only data needs RLS policies that check the user's role on the server side.

Before you ship

  • RLS on for every public table, with policies, tested with the public key.
  • No service role, secret or AI keys in front-end code or VITE_ variables.
  • Edge Functions check the user before doing anything private or paid.
  • Private buckets by default, with per-user storage policies.
  • Auth redirect URLs limited to your real domains.
  • Admin access enforced by policies, not by hiding UI.

Check your live site in 60 seconds

ShipReady scans your deployed URL and the JavaScript visitors actually download: leaked keys, Supabase exposure, unprotected API routes, titles and share images. Secret findings unlock only after you verify you own the site.

Free scan, no signup, no code upload.

FAQ

Lovable has its own security scan. Do I still need this?

Use it, it catches a lot. Then verify the deployed app: test a table with the public key and check what your live site actually serves. Scans of the project and checks of the live site catch different things.

Does this apply to Bolt, v0 or Replit apps?

Yes, for any app that uses Supabase from the browser. The table, key, function and storage checks are the same. Apps with their own server still need the key and rate-limit checks.

I found an open table. Is my data already gone?

Not necessarily, but assume it could have been read. Turn on RLS now, then check Supabase's API logs for unusual requests to that table.

Keep reading

Sources: Supabase: Row Level Security · Supabase: Securing Edge Functions · Supabase: Storage access control