Vigia › Help › Security before you launch

Security before you launch

A one-hour checklist for apps built with Lovable, Bolt, Cursor or Claude Code

By Vigia · Last reviewed 24 October 2026 against the official docs linked in each section.

AI builders get you from idea to a live URL in an afternoon. The same speed makes it easy to ship something you didn't mean to: a secret key in the JavaScript, a database table anyone can read, a .env file sitting in the web root.

This guide covers the checks that matter most before you share your link. Every recommendation links to the official documentation it comes from. Nothing here replaces a security review. It catches the common, cheap-to-fix mistakes. Vigia, which publishes this guide, sells an automated check (described in section 7); you don't need it to follow the guide.


1. Know which keys can be public

Anything the browser downloads, any visitor can read. Some keys are designed for that. Others give full access to your account and must stay on a server you control.

Key Public by design? What the docs say
Supabase publishable key (sb_publishable_…) and legacy anon key Yes, if RLS is on "Safe to expose online", it "only reaches what Row Level Security allows". [Supabase: API keys]
Supabase secret key (sb_secret_…) and legacy service_role key No "A secret key bypasses every Row Level Security policy you have. Never put one in a browser, a shipped application, or source control." [Supabase: API keys]
Stripe publishable key (pk_…) Yes "Only publishable keys are safe to expose outside your application's backend." [Stripe: API keys]
Stripe secret (sk_…) and restricted (rk_…) keys No Same page: restricted keys must be protected too. [Stripe: API keys]
OpenAI API key No "Exposing your OpenAI API key in client-side environments like browsers or mobile apps allows malicious users to take that key and make requests on your behalf." Route requests through your own backend. [OpenAI: API key safety, §3]
Firebase web API key (AIza…) Yes Public by design; access is controlled by Security Rules, IAM and App Check. [Firebase: API keys]

Watch the environment-variable prefixes. Frameworks copy some variables into the browser bundle on purpose:

Check it yourself (5 minutes):

  1. Open your .env and list every VITE_ or NEXT_PUBLIC_ name. Only the "Yes" rows in the table belong there.
  2. Open your live site, then DevTools → Sources, and search all files (Ctrl/Cmd+Shift+F) for sb_secret_, sk_live_, rk_live_ and sk-.
  3. Legacy Supabase keys are long strings starting with eyJ. The anon and service_role ones look alike from the outside, so you need to decode them (or use a check that does) to tell which one shipped. [Supabase: API keys]

Fix: move every call that needs a secret key to server-side code (an API route, a serverless/edge function, or your backend). Vite's docs suggest exactly that: "For production deployments, consider a backend server or serverless/edge functions". [Vite]

Prompt for your AI tool:

Find every secret key used in client-side code (anything with a VITE_ or NEXT_PUBLIC_ prefix, and any hard-coded key). Move each call that needs a secret key to a server-side function, read the key from a server-only environment variable, and leave only publishable keys in the browser.

Is my app safe right now? Run the free check →


2. If a secret key leaked: rotate, in the right order

Removing the key from your code is not enough: everyone who loaded the page may already have a copy. The key itself has to stop working.

Supabase (covers both a leaked secret key and a leaked legacy service_role key) [Supabase: API keys, "Rotate a leaked or compromised key"]:

  1. "Fix the root cause of the leak before you rotate anything."
  2. Create a new secret key in Settings → API Keys.
  3. Replace the compromised key everywhere your application uses it. A legacy service_role key is replaced by the new secret key.
  4. Confirm every component now uses the new key.
  5. Retire the old key: delete a secret key (this can't be undone), or, for legacy anon/service_role keys, deactivate the legacy keys (reversible).

Good to know: new secret keys return HTTP 401 when used from a browser, but Supabase warns that "an attacker can still use the key from other tools". Delete a leaked key either way.

Stripe: "Rotating an API key revokes it and generates a replacement key that's ready to use immediately." Do it from the API keys page in the Dashboard. [Stripe: API keys]

OpenAI: "If you believe your key has been leaked, rotate your key immediately." [OpenAI: API key safety, §7]

Don't commit secrets. OpenAI advises against committing keys to your repository and recommends environment variables. [OpenAI, §4] Next.js says of .env files: "You almost never want to commit these files to your repository." [Next.js] Vite recommends adding *.local to .gitignore. [Vite]

Check whether a key is still in your live site →


3. Lock your database

Supabase: Row Level Security on every table

The publishable key is public, so your data is protected by the database rules, not by hiding the key.

Grants come first. "Adding policies doesn't take those grants back. A table protected only by policies still hands anon an insert path if you never revoke the grant." Grant each role only the operations it needs. [Supabase: RLS]

Write one policy per operation (select, insert, update, delete), and watch two traps the docs call out [Supabase: RLS]:

Views can skip your policies. In Postgres 15 and above, make a view obey the RLS policies of its tables with security_invoker = true. On older versions, revoke access to the view from anon and authenticated, or keep it in an unexposed schema. [Supabase: RLS] The Advisor flags the risky case as lint 0010, "security definer view". [Supabase: Database Advisors]

Test it like a stranger would. Supabase's procedure: write a .sql file under supabase/tests/ that asserts allow and deny for select, insert, update and delete, for anon and authenticated, then run supabase test db. "Until the suite passes, you don't know whether the policies do what you intended." [Supabase: RLS]

Prompt for your AI tool:

For every table in the public schema: enable RLS, revoke grants that anon and authenticated don't need, write one policy per operation scoped to auth.uid() (with an explicit auth.uid() IS NOT NULL check), recreate every view with (security_invoker = true), and add tests under supabase/tests/ that assert allow and deny for anon and authenticated. Then run supabase test db and the Security Advisor and fix everything they report.

Firebase: Security Rules, not hidden keys

Run the free check (it tells you which Supabase key your site ships) →


4. Keep .env and .git off your web server

Check it yourself (1 minute): open these in a private window, replacing the domain with yours. Each should return "not found", never a file: https://example.com/.env · /.env.local · /.env.production · /.git/HEAD · /.git/config

If one loads: remove it from the deployment, block the path, and rotate every secret that was in it (section 2).

Check these paths automatically →


5. Cap the damage: spend limits and key restrictions

Even with good hygiene, assume a key may leak one day, and limit what it can cost.

See what's exposed today →


6. The 10-minute pre-launch run

Do this before you post your link anywhere:

  1. ☐ .env: no secret behind a VITE_ / NEXT_PUBLIC_ prefix. (§1)
  2. ☐ Live site, DevTools search: no sb_secret_, sk_live_, rk_live_, sk-. (§1)
  3. ☐ Supabase → Advisors → Security Advisor: zero security warnings. [Supabase: Database Advisors] (§3)
  4. ☐ supabase test db passes for anon and authenticated. (§3)
  5. ☐ Firebase: no rules left in Test mode. (§3)
  6. ☐ /.env, /.env.local, /.env.production, /.git/HEAD, /.git/config return "not found". (§4)
  7. ☐ Hard spend limits on every paid AI API. (§5)
  8. ☐ If anything leaked at any point: rotated, old key deleted or deactivated. (§2)

Run the free check →


7. What Vigia checks (and what it doesn't)

Vigia is a passive, read-only check for apps you own. It looks only at what any visitor can already download.

What it does:

What it doesn't do:

Price: the one-off check is free. Vigia Pro is €12/month or €120/year (VAT included) for up to 3 apps, with a weekly re-check and email alerts. Cancel anytime; full refund within 14 days.

Run the free check on your app →

— Vigia · Who we are


Sources (opened and checked 24 October 2026)

Lovable, Bolt, Cursor, Claude Code, Supabase, Stripe, OpenAI, Firebase, Vite and Next.js are trademarks of their respective owners. Vigia is not affiliated with or endorsed by them.