Is my v0 app secure?
v0 is good at a polished Next.js interface, and it will keep going into server code when you ask for data. The risky moment is when a secret is passed into a client component so the preview can render.
Free check
Check the live app
Paste the public v0 URL. The check reads what a visitor's browser can already see, then gives you a private report with a fix prompt for each issue.
What v0 actually ships
v0, from Vercel, generates React and Next.js, typically the App Router, with Tailwind and shadcn/ui. Backend logic belongs in Server Actions and route handlers. Publishing goes to Vercel. Databases such as Supabase, Neon, Upstash, and Vercel Blob are optional integrations, not something every project has. Auth is whatever integration you add. This page does not assume a database you never connected.
Mistakes worth checking
These are the ones that show up in apps built with v0. Each step is something you can do in the browser or in the product dashboard.
Critical
A secret inside a client component
A file marked with "use client", and any value a Server Component passes into one, is part of what the browser downloads. OpenAI keys, Stripe secret keys, and database passwords belong in a Server Action or a route handler. They do not belong in the props of a client form.
Check it yourself
In the project, search client components for sk-, service_role, and database URLs. Then open the deployed page and search the HTML and scripts, because a server value can still be serialized into the page.
Critical
NEXT_PUBLIC_ on a secret in Vercel
Vercel environment variables stay on the server unless the name starts with NEXT_PUBLIC_. v0 may add that prefix so the browser preview can read the value. From then on, the value is in the bundle.
Check it yourself
In the Vercel project, read the variable names. Anything secret should lack the NEXT_PUBLIC_ prefix. Search the live JavaScript for the value's prefix (sk-, sk_live, service_role) to confirm it did not ship.
High
Supabase unlocked, or a Neon URL in the browser
When v0 connects Supabase, the anon key in the client is normal and row-level security is still required. A Neon (or any Postgres) connection string includes a password and has to stay in a server variable. There is no "public" Neon key that belongs in the page.
Check it yourself
If the live page contains a supabase.co address, confirm row-level security on every public table. Search the page for postgres:// and for a Neon host. A match with a password means rotate that database password after you remove the string.
Medium
Files in public/, and browser source maps
Next.js serves the public folder as-is. An .env file dropped there becomes a URL. Browser source maps stay off unless productionBrowserSourceMaps is turned on. v0 can flip that while debugging a production error.
Check it yourself
Open /.env on the deployment URL. In next.config, confirm productionBrowserSourceMaps is not true. If a script references a .map file, open it and see whether your original source is inside.
High
Stripe's secret key used where the publishable key belongs
Checkout examples need a publishable key in the browser and a secret key on the server. Mixing them up is a one-line mistake, and the secret key can move money.
Check it yourself
Search the deployed JavaScript. pk_live and pk_test may appear. sk_ and rk_ may not. Webhook secrets start with whsec_ and belong only in the server route that verifies Stripe.
A prompt you can paste
Paste this into v0, publish again, and run the check a second time. If a key was already on the live site, rotate it in the vendor dashboard. The prompt cannot do that part.
Review this v0 / Next.js app and fix issues a visitor can already see in the deployed site. 1. Remove from client components and from NEXT_PUBLIC_ variables: OpenAI and Anthropic keys, Stripe secret or restricted keys (sk_, rk_) and webhook secrets (whsec_), Supabase service_role keys, and any database URL with a password. A Stripe publishable key (pk_) and a Supabase anon key may stay in the browser. 2. Keep those calls in Server Actions or route handlers. Read the secrets from Vercel environment variables that do not start with NEXT_PUBLIC_. Do not pass the secret into a client component as a prop. 3. If Supabase is connected, enable row-level security on every public table and add policies for the signed-in user. Do not put a Neon or Postgres connection string in client code. 4. Do not set productionBrowserSourceMaps. Do not put .env or .git inside the public folder. 5. List exposed key names so I can rotate them in the vendor dashboards. Do not print the secret values. Do not mark a server module as a client component to fix an import error if that would ship a secret.
What the check looks at
- The public page and the JavaScript it loads, for common leaked secret keys: a Supabase service role key, OpenAI, Anthropic, Stripe secret keys, AWS, GitHub, Slack, private keys, and database URLs that include a password. A Supabase anon key is called out as expected in the browser.
- A short list of public paths: /.env, /.env.local, /.env.production, /.git/config, /.git/HEAD, and /.npmrc.
- A source map, only when a script on the page already points at one, and only when that map includes original source.
- Supabase tables, only when the page already includes a supabase.co address and the public anon key. It asks for one row per table, then drops that row.
- Firebase Realtime Database and Firestore, only when the public config is already in the page. It uses that public config and does not sign in.
- A few browser safety headers on the main response. Missing headers are noted when two or more of the watched ones are absent. They are less urgent than a leaked key.
What stays outside it
- It reads. It does not log in, submit forms, or change data.
- It does not guess passwords, fuzz inputs, or hunt through hidden paths.
- It does not use any secret it finds, including a service role key.
- It does not open your builder's dashboard. A rule that lives only there can stay invisible.
- A clear result describes this public look. It is not a certificate that the whole app is safe.
- Server-only code stays out of view unless the page or a script actually includes the secret. A key that never reaches the browser will not show up here, which is what you want.
Questions
Free check
Run it on the published URL
Use the address a customer would type. The report stays private, and the shareable scorecard hides the details.
Other builders
- Is my Lovable app secure?Service role keys, row-level security, and variables that ship to the browser.
- Is my Bolt app secure?Vite variables, Supabase locks, and Bolt Database access you confirm by hand.
- Is my Replit app secure?Secrets in the page, a public Repl, and DATABASE_URL where a visitor can read it.
- Is my Cursor app secure?Client env prefixes, row-level security, Firebase test mode, and source maps.
- Is my Base44 app secure?Entity rules you check by hand, plus Vite secrets and service-role calls.
- Is my Windsurf app secure?Agent edits that ship a secret, loosen a database rule, or publish a source map.
- Is my Bubble app secure?Privacy rules, API Connector secrets, and the Data API admin token.