v0 app security checklist (from a security engineer)
By Pritesh Ghodge, Senior Solutions Engineer (cybersecurity) · Last updated 7 November 2026
Check your published v0 app from the outside first.
Abbi Labs looks at your live site the way a stranger would: secret keys in the JavaScript, Supabase tables readable with your public key, exposed .env and .git files, source maps, and security headers. No sign-up for the quick check.
No sign-up. Read-only public-resource and protocol metadata checks. No model prompts or tool execution. Scan only sites you own or are allowed to test.
Run on every deploy or use from Cursor / Claude Code via MCP →
Weekly AI-app security checklist
One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.
v0 gets you from a prompt to a Next.js app on Vercel very quickly, and its security docs are clear about the NEXT_PUBLIC_ boundary and moving secrets into Route Handlers or Server Actions. What it can't know is what your data is supposed to be: which tables are public, which belong to one user, which keys can spend your money. That's where most problems in AI-built apps come from. Here is the checklist I'd run on a v0 app before sharing it with real users.
1. Credit what v0 already does, then check the live URL
According to v0's docs:
- Generated code is analysed before execution, and runs in sandboxed environments.
- Only variables prefixed with
NEXT_PUBLIC_are exposed to the browser; unprefixed ones stay on the server. - v0 analyses
NEXT_PUBLIC_usage, can warn about risky ones, and can refactor calls into Route Handlers, Server Actions or other server-side contexts. - On Vercel, server Functions run in isolated containers with encrypted environment variables.
Use those tools while you build. Then open the published URL in a private window and treat it like a stranger's site. The rest of this list is that outside view.
2. No secret keys in NEXT_PUBLIC_ or Client Components
Anything with a NEXT_PUBLIC_ prefix is inlined into the JavaScript every visitor downloads. Fine for your site URL, analytics IDs and a Supabase publishable/anon key. Not fine for:
- the Supabase
service_roleorsb_secret_key (it bypasses RLS completely), - Stripe secret keys and webhook signing secrets,
- OpenAI, Anthropic, Resend or any other API key that costs money or sends mail as you,
- database connection strings.
Those belong in Vercel Environment Variables without the NEXT_PUBLIC_ prefix, read only from Server Components, Server Actions or Route Handlers. If a key was ever in the browser bundle, rotate it at the provider. Removing it from the code doesn't un-leak it.
Quick check on your published site: open DevTools → Sources (or search the JS files) for sk_live, sk-, sb_secret_ and service_role. More detail: NEXT_PUBLIC_ and VITE_ variables are shipped to every visitor.
3. Every Supabase table has RLS on, and policies check whose row it is
Many v0 apps wire up Supabase. The publishable key in the browser is fine; Row Level Security is what stops that key reading everything. Two traps:
- A table with RLS off is readable (and often writable) by anyone with the key.
- RLS on with a policy of
using (true), or "any signed-in user", lets everyone (or every account) through. Private tables need(select auth.uid()) = user_id.
The 60-second test (use your project URL and the publishable key from your app):
curl -s "https://YOUR-PROJECT.supabase.co/rest/v1/orders?select=*&limit=1" \
-H "apikey: YOUR_PUBLIC_KEY" \
-H "Authorization: Bearer YOUR_PUBLIC_KEY"[] or a permission error is what you want for private tables. A row of real data means anyone can read that table. Full walkthrough: how to check if your Supabase tables are readable by anyone. Why the anon key itself is meant to be public: is your Supabase anon key safe to expose?
For write policies an outside scan can't test (open INSERT/UPDATE, with check (true), grants), use the free Supabase RLS policy checker: one read-only SQL query, paste the result, analysed in your browser.
A prompt you can paste into v0:
List every RLS policy in my database as a table: table, command
(select/insert/update/delete), role, and the exact USING and WITH CHECK
conditions. Flag RLS off, using (true), with check (true), and any
user-owned table whose policies never mention auth.uid(). Propose
owner-only policies. Show me the SQL before applying it.4. Server Actions and Route Handlers are still public endpoints
Moving an OpenAI or Stripe call into a Server Action or Route Handler hides the key. It does not hide the endpoint. If it doesn't check who is calling, anyone can use it to spend your credits or do whatever the function does. For each one, ask:
- Does it require a signed-in user and verify the session, or is it deliberately public?
- Does it check the user is allowed to act on the record they passed in (not just that they're signed in)?
- Does it calculate prices on the server instead of trusting an amount from the browser?
- For payment webhooks: does it verify the provider's signature?
- Is there a limit on how often one user can call it, if each call costs you money?
More detail in AI API key exposed in your frontend.
5. Nothing extra is published with your site
From a browser, try /.env, /.git/HEAD and a .map next to one of your JS files on your published URL. You want a 404 or your app's normal page, not a file. Next.js doesn't emit production source maps unless you turn them on; check productionBrowserSourceMaps (and any custom webpack/turbopack map settings) if you or the AI changed them. See exposed .env, .git and source maps.
6. Security headers on your Vercel host
A Content-Security-Policy, HSTS and clickjacking protection (X-Frame-Options or CSP frame-ancestors) are set by your host config, not by the v0 editor. Check what your published site sends with curl -sI https://your-app.vercel.app (and again on any custom domain) and add the missing ones in vercel.json or next.config headers. Examples: security headers for vibe-coded apps.
7. The two-account test
No checklist beats this. Sign up as user A and create some data. Sign up as user B in a private window. As B, try to open A's pages by changing IDs in the URL, and watch the network tab for requests that return A's rows. Then sign out and try again as a visitor with the public key (the curl above). Only test projects you own.
What an outside scan adds, and what it can't do
v0's analysis reads your project while you build. An outside scan checks what the published site actually serves to a stranger: the built JavaScript, the files on the host, the headers, and what your public key can read. It can't see your business rules or test as a signed-in user, so it doesn't replace the two-account test or reading your policies. Using both catches different mistakes.
Abbi Labs' free quick check covers leaked keys in JS, Supabase tables anyone can read with the public key (limit=1), exposed .env/.git, and security headers. Full scans on a verified domain also cover public storage buckets and source maps. For write-side RLS holes, pair it with the free RLS policy checker.
Keep it that way
Every prompt can change a policy or add a NEXT_PUBLIC_ variable, including an AI "fix" for an error. Abbi Labs scans your live app for leaked keys, Supabase tables readable with your public key, exposed .env/.git files and security headers. Monitor re-scans weekly and after deploys, and emails you only when something new appears.
FAQ
Does v0 check my app's security?
Per v0's security docs, generated code goes through security analysis before execution, and v0 analyses NEXT_PUBLIC_ usage and can warn about, or refactor, secrets that should stay on the server. That helps while you're building. It does not replace checking what your published Vercel URL actually serves to a stranger: the built JavaScript, Supabase policies, exposed files and headers.
Is it safe that my Supabase key is visible in my v0 app?
The publishable (anon) key is designed to be public, as long as Row Level Security is on for every table and each policy only allows what you intend. Secret keys (service_role or sb_secret_, Stripe secret keys, OpenAI and other API keys) must never use a NEXT_PUBLIC_ prefix or appear in Client Components.
What does "v0 vercel security" usually miss?
The usual misses are the same ones Abbi Labs scans for from the outside: a secret key inlined into the browser bundle via NEXT_PUBLIC_, Supabase tables readable with the public key because RLS is off or using (true), a production source map or .env/.git left reachable, and missing security headers on the custom domain.
If I use Server Actions, are my secrets safe?
Unprefixed environment variables stay on the server, which is good. The Server Action URL is still callable by anyone who knows how. Treat every Server Action and Route Handler as a public endpoint: check the session, authorise the specific row, validate input, and rate-limit anything that costs money.
Does Abbi Labs work with v0 apps?
Yes. Paste your published Vercel or custom-domain URL. The free quick check needs no sign-up and looks at your home page headers, its JavaScript for secret keys, /.env and /.git, and whether Supabase tables your code queries (plus common names) return rows with your public key. Full scans on a domain you've verified you own add storage buckets and source maps.
Related guides
- Lovable app security checklist (from a security engineer)
- Is my Bolt.new app secure? A security checklist before you publish
- How to check if your Supabase tables are readable by anyone
- Is your Supabase anon key safe to expose?
- NEXT_PUBLIC_ and VITE_ variables are shipped to every visitor
- 3 files your production site shouldn't be serving: .env, .git and source maps
- Free tool: Supabase RLS policy checker