Heimdall Scan logo, AI security scanner for vibe coders
Heimdall ScanOpen Beta
Guide

Secure your Lovable app

Lovable ships fast because it makes a lot of decisions for you. Most of those decisions are fine. The Supabase ones are the ones to double-check. Out of the box your database tables are open, your auth is loose, and your storage buckets sometimes don't care who's asking. Here's what to fix before users show up.

  1. 01

    Turn on Row Level Security for every table

    This is the big one. Supabase makes RLS available but doesn't enforce it by default, and Lovable usually doesn't turn it on for you. Without RLS, the anon key (which is sitting in your frontend code) lets anyone read every row in every table. "Anyone" includes someone who just opened DevTools.

    Paste this into Cursor / Lovable / Bolt
    List every table in my Supabase project. For each one, enable RLS and write a policy that restricts SELECT, INSERT, UPDATE, and DELETE to rows where the user_id column equals auth.uid(). For tables without a user_id, ask me what the ownership rule should be.
    Read the deep-dive: Broken access control
  2. 02

    Keep the service role key out of the client

    The anon key is meant for the browser. The service role key bypasses RLS and is meant for server code only. If Lovable ever pastes it into a component, or it gets a VITE_ prefix and ships with the build, every guard you set up is gone.

    Paste this into Cursor / Lovable / Bolt
    Grep this project for SUPABASE_SERVICE_ROLE_KEY. Make sure it only appears in server-side code (Edge Functions or API routes), never in src/ files that get shipped to the browser. If it shows up in client code, replace the call with an Edge Function.
    Read the deep-dive: Exposed API keys
  3. 03

    Make sure .env doesn't ship

    Vite reads VITE_* variables and bakes them into the client bundle. That's expected for the public ones (URL, anon key). It's not expected for anything else, and it's catastrophic if a real secret accidentally gets the VITE_ prefix.

    Paste this into Cursor / Lovable / Bolt
    Audit my .env. Anything prefixed VITE_ will be visible to anyone using my site. Confirm only the Supabase URL and anon key use that prefix. Move all other secrets to server-only env vars and verify .env is in .gitignore.
    Read the deep-dive: .env file exposure
  4. 04

    Force email confirmation on signup

    Supabase auth lets you flip "confirm email" off so testing is faster. Lovable often leaves it off. In production that means anyone can create an account claiming any email. Useful for spammers, terrible for password reset flows.

    Paste this into Cursor / Lovable / Bolt
    In my Supabase project settings, confirm that email confirmation is required. If I'm using a custom signup flow, make sure I'm checking the email_confirmed_at field before granting access to protected routes.
    Read the deep-dive: Unprotected API routes
  5. 05

    Rate limit the auth endpoints

    Without rate limits, anyone can hammer your login endpoint forever, brute-forcing passwords or burning through your auth provider's quota. Supabase has built-in caps but they're generous. Tighten them or add a Cloudflare or Vercel layer on top.

    Paste this into Cursor / Lovable / Bolt
    Add rate limiting to my login and signup endpoints. Cap login attempts to 5 per minute per IP and 20 per minute per email. Return 429 with a clear error when the cap is hit.
    Read the deep-dive: API rate limiting
  6. 06

    Validate every Edge Function input

    Edge Functions get called straight from your frontend, which means the input is fully attacker-controlled. Don't trust the shape, the types, or the values. Use Zod or valibot.

    Paste this into Cursor / Lovable / Bolt
    For every Supabase Edge Function in this project, add a schema validation step at the top using Zod. Reject the request with a 400 if validation fails. The schema should be strict about types, lengths, and allowed values.
    Read the deep-dive: API input validation
  7. 07

    Force HTTPS on your custom domain

    Lovable hosts on an HTTPS subdomain by default. When you connect a custom domain through Vercel or Netlify, make sure the HTTP-to-HTTPS redirect is on and add HSTS. Cookies need the Secure flag too.

    Paste this into Cursor / Lovable / Bolt
    Confirm my custom domain redirects HTTP to HTTPS at the host level. Add a Strict-Transport-Security header with max-age=31536000. Make sure any auth cookies are set with Secure and SameSite=Lax.
    Read the deep-dive: HTTPS enforcement
  8. 08

    Update your privacy policy to name Supabase

    If your app uses Supabase, your privacy policy needs to mention it. Same for Stripe, PostHog, any analytics, any email provider. Stripe's onboarding will reject you if your policy doesn't name your subprocessors.

    Paste this into Cursor / Lovable / Bolt
    Generate or update my privacy policy. List every third party that processes user data: Supabase (database and auth), and anything else this app uses (Stripe, analytics, email). Cover what data each one sees and where users can request deletion.
    Read the deep-dive: Missing privacy policy

Lovable's defaults are tuned for shipping a prototype, not running a production app. The gap between "looks fine on the demo URL" and "safe with real users" is mostly the eight items above. Run Heimdall once your app is past the demo stage and you'll catch the ones you missed.