Guide
Vibe-coded app security: the checklist for Lovable, Bolt, Cursor, v0 and Vercel apps
AI builders get you from idea to live app in an afternoon. They don't always get the boring security settings right. Here are the eight checks to run before you share the link, what each one means, and how to fix it on the platform you used.
Last updated
The checklist at a glance
| Check | How to verify it |
|---|---|
| No secret keys in front-end code or public env vars | Search your built JavaScript and any VITE_ / NEXT_PUBLIC_ variables for secret keys. |
| Supabase: RLS on every table, grants only where needed | Check Table Editor or Security Advisor, then test while signed out. |
| No public .env file or .git folder | Request /.env and /.git/HEAD on your own domain; both should 404. |
| Production source maps off or not deployed | Look for readable src files in your browser's Sources panel. |
| CORS allowlist, never reflect any Origin with credentials | Check the CORS settings on every API route or function. |
| Security headers set | HSTS, CSP (at least frame-ancestors), nosniff, Referrer-Policy. |
| Paid APIs called only from the server, with auth and limits | Edge Functions, API routes or server actions, each checking the user. |
| Preview deployments protected and using non-production secrets | Vercel Deployment Protection, separate Preview env vars. |
Each item gets its own section below, with links to step-by-step fix guides on StackSiren, our passive scanner for AI-built apps.
1. Keep secret keys out of the browser
Anything in your front-end code is public. Visitors download your JavaScript, and anyone can open it in developer tools. That's fine for keys that are designed to be public, and a real problem for the rest.
Public prefixes are not secret
Vite bundles every variable starting with VITE_ into client code at build time, and Next.js does the same with NEXT_PUBLIC_. Other frameworks have their own prefixes, such as REACT_APP_, EXPO_PUBLIC_ and PUBLIC_. AI builders sometimes add one of these prefixes to make an "undefined" error go away, which quietly publishes a server secret. Because the value is baked in at build time, deleting the variable later doesn't remove it from deployments that already exist. Rotate the key.
Which keys can be public
- Designed to be public: the Supabase publishable key (
sb_publishable_...) or legacy anon key, a Stripe publishable key (pk_...) and Firebase web config. - Never public: the Supabase secret key (
sb_secret_...) or legacyservice_rolekey, OpenAI and Anthropic keys, Stripe secret and restricted keys (sk_...,rk_...), AWS access keys, GitHub tokens, database connection strings and email-service keys.
Fix: move calls that need a secret into server code (a Supabase Edge Function, a Next.js route handler or server action, a Netlify Function) and store the key in that environment's secrets. Then rotate every key that was ever exposed. StackSiren's guide to exposed .env files and VITE_ secrets covers the prefixes and the rotation steps in more detail.
2. Supabase: Row Level Security and grants
Lovable and Bolt apps often use Supabase, and the browser talks to the database directly through Supabase's Data API using the public key. Access control therefore lives in Postgres itself, in two layers:
- Grants decide whether a role (
anon,authenticated) can reach a table at all. - Row Level Security (RLS) policies decide which rows that role can see or change, for example only rows where
auth.uid() = user_id.
If a table has RLS turned off and is granted to anon, anyone holding your public key can read or change every row.
Heads up: Supabase is changing how new tables are exposed on 30 October 2026. Supabase stops auto-exposing new tables on 30 October 2026 explains what changes and what to do.
The 30 October 2026 change
According to Supabase's changelog, new tables in the public schema used to be granted to anon, authenticated and service_role automatically. That stopped being the default for new projects from 30 May 2026, and it applies to all existing projects from 30 October 2026. After that, a new table isn't reachable through the Data API until you add an explicit GRANT, and the API returns error 42501 ("permission denied for table") in the meantime. Existing tables keep their grants, and RLS works exactly as before.
The tempting fix for a 42501 error is to grant everything to anon and move on. Don't. Grant only what each role needs, and enable RLS and add policies in the same migration.
Verify: in the Supabase dashboard, confirm RLS is enabled on every table in public, and check the Security Advisor, which flags tables without RLS. Then test while signed out: a request using only your public key should return an error or an empty list, never other people's data. Remember storage buckets too, which are controlled by policies on storage.objects. StackSiren's Supabase RLS guide walks through the checks.
3. No public .env file or .git folder
Sometimes the leak isn't in the code at all, but in a file that was deployed by accident. If a .env file lands in a Vite public/ folder, or a server publishes the whole project directory, anyone can download it from yourdomain.com/.env. A served .git folder is worse: people can often rebuild your source code and full commit history, including secrets you committed and later deleted.
Verify: on your own domain, open /.env and /.git/HEAD. Both should return a 404, not a file. Fix: add .env* to .gitignore, keep secrets in your host's environment settings, block dotfiles on any server you run yourself, and rotate anything that was exposed. Removing a secret from the latest commit doesn't remove it from Git history. See StackSiren's exposed .env and .git guide.
4. Turn off public source maps
A source map (.map file) links minified JavaScript back to your original code. Most maps include the full source, so a public one shows your file structure, comments, internal API routes and business logic as written. It doesn't add secrets that weren't already in the bundle, but it makes everything much easier to read.
- Vite:
build.sourcemapis off by default. If maps are public, look forsourcemap: trueor'inline'invite.configand remove it. - Next.js: browser source maps are only produced in production when
productionBrowserSourceMaps: trueis set innext.config. Remove it unless you need it. - Error tracking: if you want readable stack traces, upload maps to your error tracker (such as Sentry) and delete them from the deployed output.
Verify: open your live site, then the Sources panel in developer tools. If you can browse a readable src folder, your maps are public. StackSiren's source maps guide has the exact settings for Vite, Next.js and Sentry setups.
5. Get CORS right
CORS is a browser rule that decides whether JavaScript on another website can read your API's responses. It isn't authentication: tools like curl ignore it, so every endpoint still needs its own login and permission checks.
Access-Control-Allow-Origin: * is fine for public data that needs no login, and browsers refuse to combine it with cookies. The dangerous pattern is code that copies whatever Origin header arrives into the response and sends Access-Control-Allow-Credentials: true. Then any site a signed-in user visits can read their data. AI-generated code sometimes does exactly this to make a CORS error disappear. Allowing the null origin and loose matching ("ends with example.com", which also matches evilexample.com) are related mistakes.
Fix: allow only your own origins by exact match, send Vary: Origin, and only allow credentials for those origins. StackSiren's CORS misconfiguration guide explains each pattern with fixes.
6. Add security headers
Security headers are instructions your server sends with every page. They take minutes to add and close off whole classes of attack:
Strict-Transport-Security(HSTS) tells browsers to use HTTPS only.Content-Security-Policylimits where scripts and other resources can load from. Itsframe-ancestorsdirective (or the olderX-Frame-Options) stops other sites framing yours for clickjacking.X-Content-Type-Options: nosniff,Referrer-PolicyandPermissions-Policyare low-effort hardening.
On Vercel, return them from headers() in next.config or add them to vercel.json. On Netlify, use a _headers file or netlify.toml. If you're unsure about a strict CSP, start it in report-only mode and tighten it once nothing breaks.
7. Protect your server functions and paid APIs
Moving a key to the server is step one. Step two is making sure the server endpoint can't be abused. A route that forwards any request to OpenAI is still an open wallet, just with the key hidden.
- Check that the user is signed in, and allowed to do the action, inside every API route, Edge Function and server action. Next.js server actions can be called directly, so treat each one like a public endpoint.
- Never trust prices, user IDs or roles sent from the browser. Look them up on the server.
- Verify webhook signatures (for example Stripe's) before acting on a webhook.
- Add rate limits to expensive endpoints, and set usage or budget limits in your AI provider's dashboard where it offers them.
- If you use Firebase, check your Security Rules before launch, since test-mode rules allow open access.
8. Lock down preview deployments
Vercel and Netlify create a deployment for every branch or pull request. That's great for testing, but a preview that's publicly reachable and wired to production data or keys is a second, less-watched copy of your app. On Vercel, use Deployment Protection for previews and keep production secrets out of the Preview environment, which has its own set of environment variables.
Platform notes: Lovable, Bolt, Cursor, v0 and Vercel
The checklist applies everywhere, but each tool tends to trip over different items.
Lovable
Lovable builds React front-ends and usually pairs them with Supabase for the database, auth and storage. Focus on items 1, 2 and 7: RLS and grants on every table, and paid API calls (OpenAI and the like) moved into Supabase Edge Functions with keys in function secrets. See StackSiren's Lovable checklist.
Bolt
Bolt (bolt.new, by StackBlitz) builds full-stack JavaScript apps that are often deployed to Netlify or exported elsewhere. Watch for keys in VITE_ variables, Supabase tables without RLS and production source maps (items 1, 2 and 4). See the Bolt checklist.
Cursor
Cursor is an editor, so hosting is whatever you set up, and headers, redirects and server config are your job. Review generated code for secrets used in client components, make sure .env is in .gitignore, block dotfiles on self-hosted servers, and check any CORS code it wrote (items 1, 3, 5 and 6). See the Cursor checklist.
v0
v0 by Vercel generates React and Next.js apps that deploy to Vercel. The common gaps are secrets in NEXT_PUBLIC_ variables and no security headers (items 1 and 6). See the v0 checklist.
Vercel
Whatever built your app, if it runs on Vercel, check NEXT_PUBLIC_ variables, add headers in next.config or vercel.json, and protect previews (items 1, 6 and 8). StackSiren's Vercel security checklist covers each of these.
How to check all of this quickly
You can do every check above by hand in under an hour with your browser's developer tools and your Supabase dashboard. If you'd like a first pass done for you, StackSiren's free scan takes your app's public URL and makes a few normal GET requests, the same ones any visitor's browser makes. It looks for exposed keys in your JavaScript, Supabase and Firebase config, security headers, HTTPS, publicly reachable files such as .env, .git and source maps, and SEO basics. You get a free score and issue list, and paid reports add copy-paste fix prompts for your AI builder.
Be clear about what a passive scan can't do. It never logs in and never queries your database, so it can't tell you whether your RLS policies, grants or server-side permission checks are correct. Those still need the manual tests in sections 2 and 7. Only scan apps you own.
Disclosure: StackSiren is made by StackBySolo, the team behind this site. Sources: Supabase changelog (Data API grants change), Vite and Next.js environment variable and build documentation, MDN on CORS and security headers, and Vercel's Deployment Protection docs, checked 18 October 2026.
Frequently asked questions
Is it safe to have my Supabase anon or publishable key in the browser?
Yes, that key is designed to be public. It is only safe if Row Level Security is enabled on every table in an exposed schema, with policies that limit what each user can read and write. The Supabase secret key (sb_secret_...) or the legacy service_role key must never be in front-end code, because they bypass RLS completely.
Are VITE_ and NEXT_PUBLIC_ environment variables secret?
No. Vite and Next.js copy variables with those prefixes into the JavaScript that every visitor downloads, at build time. Treat them as published. Keep API keys for OpenAI, Anthropic, Stripe secret keys, databases and email services in server-only variables, and rotate any key that was ever given a public prefix.
What changes in Supabase on 30 October 2026?
Supabase's changelog says that from 30 October 2026, new tables in the public schema of existing projects are no longer granted to anon, authenticated and service_role automatically. The Data API returns error 42501 (permission denied) until you add an explicit GRANT. Existing tables keep their grants. Grant only what each role needs and enable RLS with policies in the same migration.
Do I need security headers on a small app?
They are cheap to add and worth it. HTTPS with HSTS, a Content-Security-Policy (or at least frame-ancestors), X-Content-Type-Options: nosniff and a Referrer-Policy cover the basics. On Vercel you can set them in next.config headers() or vercel.json.
Is Access-Control-Allow-Origin: * a security hole?
Not for public data that needs no login. The real risk is a server that reflects any Origin header back and also sends Access-Control-Allow-Credentials: true, which lets other sites read signed-in users' data. Use an exact allowlist of your own origins instead.
Does a passive scan replace a penetration test?
No. A passive scan, like StackSiren's, only reads what any visitor's browser can already see: public pages, JavaScript bundles, response headers and publicly reachable files. It can't check your database policies, business logic or authentication flows. It's a quick first pass, not a full audit.
Building the rest of your solo business? See our solopreneur tech stack and best AI tools for solopreneurs.
The Solo Stack, every week
One AI tool deep-dive, three quick finds and one workflow to copy. Written for people who run their business alone.