v0 security: is v0 safe, and where v0 apps still leak

v0 is safe to build with: it analyzes generated code before running it in sandboxes, warns about secrets behind NEXT_PUBLIC_ and is covered by Vercel's SOC 2 Type 2 attestation. It does not decide who may call your Server Actions, API routes and Supabase tables. That authorization code is yours, and it is where v0 apps leak.

The team behind Keybrum's attacksUpdated 15 min read

In short

  • v0 builds Next.js App Router apps and deploys them to Vercel. Databases come from the Vercel Marketplace: Supabase, Neon, Upstash and Vercel Blob.
  • Every exported Server Action answers a direct POST request, even when no button calls it. Check the session and the record's owner inside each one.
  • Connecting Supabase syncs 13 variables into your project, and only the URL and publishable key carry NEXT_PUBLIC_. The secret key, the JWT secret and the Postgres connection string belong in server code, stored as Vercel Secret variables: Vercel's April 2026 incident exposed variables that were not marked sensitive.
  • Middleware alone is not authorization: Next.js CVEs in 2025 and 2026 let requests past it. The November 2026 security release is Next.js 16.4.5 or 15.5.29.
  • A chat set to Anyone on the web may be indexed by search engines with its whole history. Check the visibility of any chat where you pasted a key, a schema or customer data.
On this page

Is v0 safe? The short answer

Yes, as a place to build. v0 is Vercel's AI app builder: you describe an app, it writes the code, runs it in a sandbox and publishes it to Vercel. Its security documentation says it treats all generated code as potentially incorrect or adversarial, analyzes it before execution and runs it in isolated environments. Once deployed, server code runs as Vercel Functions in isolated containers, environment variables are stored in what v0 calls Vercel's secure vault, and v0 is inside the scope of Vercel's SOC 2 Type 2 attestation.

The app you publish is another matter. v0's FAQ answers "Is my code secure?" with a yes that points to secure coding practices and Vercel's infrastructure. Infrastructure does not decide whether a stranger can call your deleteProject action with someone else's id, read a Supabase table, or run up your AI bill through /api/chat. Those few lines of authorization code are yours, and generated apps get them wrong most often.

What v0 and Vercel handle, and what you handle

Areav0, Vercel and Next.jsYou
Generated codeAnalyzed before it runs, executed in a sandboxReading the server code before you publish
Environment variablesStored in Vercel's vault, write-only when typed Secret; only NEXT_PUBLIC_ variables reach the browser; v0 warns about risky NEXT_PUBLIC_ useChoosing which variables carry the prefix, storing keys as Secret, and rotating any secret that was ever exposed
Server Actions and API routesHard-to-guess action ids, unused actions dropped from the bundleChecking the session and the record's owner inside every action and route handler
DatabaseOne-click provisioning and variables through the Vercel MarketplaceRLS, grants and policies in Supabase; owner filters in every Neon query
Framework CVEsRouting that kept Vercel-hosted apps out of CVE-2025-29927; React2Shell patches pushed to v0 chatsKeeping next and react at the patched versions in package.json
Who sees whatChat visibility, Deployment Protection, AI spend limitsSetting them: the chat's audience, the protection scope, the limits
Sources: v0's security, databases, sharing and deployments docs, the Next.js data security guide and Vercel's postmortem and React2Shell bulletin, all linked below.

The stack v0 builds, and where it leaks

v0 defaults to Next.js with the App Router. Its docs say it uses React Server Components, creates backend endpoints with App Router conventions and connects to databases such as Supabase, Neon and Upstash without an ORM by default. Integrations come from the Vercel Marketplace, under Project menu > Settings > Integrations: Supabase, Neon, Upstash and Vercel Blob for data, Grok, fal and Deep Infra for AI. For SQL databases, v0 can generate and execute SQL itself, so it creates, changes and drops your tables.

PartWhat v0 generatesWhere it leaks
Server Actions ("use server")Form handlers and mutations next to the UIAnyone can POST to an exported action. Without a session and owner check, anyone can change anyone's data
Route handlers (app/api/.../route.ts)JSON endpoints, webhooks, AI chat routesSame rule: no check means public. AI routes spend your credits for strangers
middleware.ts or proxy.tsRedirects to sign-in for protected pathsUsed as the only lock, and CVEs have slipped requests past it
SupabaseTables created by SQL, Supabase Auth, the publishable key in the browserTables without RLS that anon can read or write; policies that check sign-in but not ownership
NeonDATABASE_URL used from server codeEvery visitor shares one database role, so the database cannot tell users apart: every rule lives in your queries
Environment variablesKeys added by integrations and by youA secret renamed with NEXT_PUBLIC_ ends up in the JavaScript every visitor downloads

Sign-in deserves a sentence of its own. v0's docs do not describe a sign-in system of its own: you prompt for it, and the full-stack guide's example prompt is "Add user authentication so users can only see and manage their own tasks." The second half of that sentence is the part to verify. A sign-in page can work perfectly while the server answers anyone who calls it directly.

Concrete risks in v0 apps

Server Actions that skip the owner check

Next.js documents that an exported Server Action is reachable by a direct POST request, not only through your UI, even when no file imports it. Next.js makes action ids hard to guess and drops unused ones from the bundle, then says plainly that you should still verify authentication and authorization inside each action. The ids of the actions your pages use ship in the JavaScript. A generated updateTask(id, data) that runs update tasks set ... where id = $1 lets anyone who can send a request edit any task. That is an IDOR, and "signed in" checked without "owns this row" is the access control bug we find most often in generated apps.

Sign-in checked only in middleware

Generated apps often protect /dashboard with a redirect in middleware.ts (proxy.ts in recent Next.js versions) and nowhere else. Middleware is a good place for a redirect and a poor place for the only check. CVE-2025-29927 let one request header skip middleware on self-hosted Next.js; Vercel's postmortem explains that apps hosted on Vercel were never affected, because its routing runs in a separate system. CVE-2026-44575, rated 7.5 (high) in GitHub's advisory, let crafted .rsc and segment-prefetch URLs reach App Router pages without the middleware rule matching, in Next.js 15.2.0 up to 15.5.16 and 16.0.0 up to 16.2.5. That first fix was incomplete: a follow-up, CVE-2026-45109, closed the gap in 15.5.18 and 16.2.6. The lasting fix is the same for both: check the session again where the data is read.

Secrets one prefix away from the browser

Installing Supabase from the Vercel Marketplace syncs 13 environment variables into your project. Two carry the NEXT_PUBLIC_ prefix and are meant for the browser: NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY. Next to them sit SUPABASE_SECRET_KEY, SUPABASE_JWT_SECRET, POSTGRES_URL and POSTGRES_PASSWORD, none of which belong in a browser. Neon adds DATABASE_URL and its parts the same way. Browser code cannot read a variable without the prefix, so when a client component needs a value, the quickest fix an agent can find is to add the prefix. v0 analyzes NEXT_PUBLIC_ usage and warns you, which helps, but the warning only works if someone reads it. Our guide to API keys exposed in the frontend lists which keys may be public.

Where the values sit matters too. In April 2026, Vercel disclosed that an attacker who came in through a third-party AI tool used by one of its employees decrypted environment variables that a limited subset of customers had not marked as sensitive. Vercel advises reviewing and rotating every such value. Vercel now types each variable as Config, readable after saving, or Secret, write-only. Variables created by an integration, like the 13 from Supabase, cannot be switched to Secret in place: Vercel's steps are to disconnect the integration, rotate its secrets, reconnect it with Development unchecked and Sensitive turned on, then redeploy.

Supabase tables created by SQL

When Supabase is connected, v0 writes and runs the SQL that creates your tables. Supabase's own rule is short: a table in an exposed schema without RLS (row level security) is readable and writable by any role with a grant on it, and on projects that still grant by default, a new table in public starts with every privilege granted to anon and authenticated. The publishable key in your bundle acts as anon. If a create table ran without enable row level security, the table answers anyone. The Supabase RLS guide has the policies for the common cases.

There is a quieter case. Server code that queries through POSTGRES_URL connects as a database user, not as your visitor. Supabase's docs note that its postgres role has bypassrls, and PostgreSQL says a table's owner normally bypasses row security too. Check which user POSTGRES_USER names in your Vercel variables: if it is postgres or the owner of your tables, RLS protects nothing on that path, so the owner check has to sit in the query or in the action that runs it. The same goes for every query to Neon. The Supabase secret key also skips RLS, unless the request carries the signed-in user's access token.

AI routes anyone can call

v0 can connect AI providers such as Grok, fal and Deep Infra, and the setup lets you set spend limits and auto top-ups: set them. A route like /api/chat or /api/generate that calls the model without checking the session lets any visitor, or any script, use your key at your cost. The guide on unprotected AI endpoints covers the fix in depth.

Chats, previews and shared caches

A v0 chat holds your prompts and your code. Its visibility can be Private, your team, Anyone with the link (not indexed) or Anyone on the web, which may be indexed by search engines and appear in public galleries. New team chats are visible and editable by the whole team unless a team Owner changes the default, and Owners can open any team chat by its URL. If you pasted a key, a schema or customer data into a chat, check its audience in the Invite dialog.

For deployments, Vercel's Standard Protection covers every deployment except production domains, and All Deployments covers production too. v0 asks you to choose visibility at first publish. Last, the Next.js November 2026 security release, 16.4.5 and 15.5.29, fixes cache bugs that could serve one tenant's or one user's cached result to another in apps that use Cache Components. Our Next.js security guide covers the full CVE list.

How to check your v0 app

Start with what you can read in v0's code view or in your GitHub export, then test the live app signed out. Everything below runs against your own project.

  1. 1

    Check the Next.js version

    Open package.json. next should be 16.4.5 or later on the 16 line, or 15.5.29 or later on 15.5: the versions set by the November 2026 security release. Paste the file into the Next.js CVE checker to see every CVE that applies to your versions of Next.js and React.

  2. 2

    List what reaches the browser

    Search the code for NEXT_PUBLIC_, then open Project menu > Settings > Environment Variables in v0. Only the Supabase URL, the publishable key and values you would happily print on your home page belong behind the prefix. Any secret there is already in your JavaScript. Then, in the Vercel dashboard, check that every key carries the Secret label.

  3. 3

    Find every server entry point

    Search for "use server" and list the route.ts files under app/api. For each exported function, find the line that reads the session and the line that checks the record belongs to that user. A function with neither is public.

  4. 4

    Read your middleware

    Open middleware.ts or proxy.ts. If it is the only place that checks sign-in for a page or an API route, add the check to that page's data loading and to the route itself.

  5. 5

    Check the database

    With Supabase, open Security Advisor in the Supabase dashboard and run the query from the Supabase RLS checker. With Neon, every query in an action or route needs an owner filter built from the session, never from the request body.

  6. 6

    Check who can see the chat and the deployments

    Open the chat's Invite dialog. Then open Project menu > Settings > Vercel Project for production visibility, and the Deployment Protection settings in Vercel for previews.

bash
# In the root of your exported v0 project
grep -rn "NEXT_PUBLIC_" app components lib --include=*.ts --include=*.tsx
grep -rln "use server" app components lib
find app/api -name "route.*"
grep -rln "SUPABASE_SECRET_KEY\|SUPABASE_SERVICE_ROLE_KEY\|SUPABASE_JWT_SECRET\|POSTGRES_URL\|DATABASE_URL" app components lib
Every file the last command lists must be server code: a Server Action, a route handler or a module that starts with import 'server-only'. A match in a file marked "use client" is a leak.

Then test one AI route the way a stranger would, with no cookie:

bash
curl -i -X POST "https://YOUR-APP.vercel.app/api/chat" \
  -H "content-type: application/json" \
  -d '{"messages":[{"role":"user","content":"Say hi"}]}'
Run it against your own app only, and adjust the path and body to match your route. A model's answer means strangers can use your AI key. A 401 is what you want.

Fixes, with prompts for v0

v0 edits code from the chat, so the fastest fix is a precise prompt. Send these one at a time and read the diff before you publish.

Check the owner inside every action

ts
// Before: anyone who can send a POST can delete any project
"use server";
import { sql } from "@/lib/db";

export async function deleteProject(id: string) {
  await sql`delete from projects where id = ${id}`;
}

// After: the session and the owner are checked inside the action
"use server";
import { createClient } from "@/lib/supabase/server";

export async function deleteProject(id: string) {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) throw new Error("Not signed in");

  const { error } = await supabase
    .from("projects")
    .delete()
    .eq("id", id)
    .eq("owner_id", user.id);
  if (error) throw error;
}
The second version runs as the signed-in user, so Supabase RLS applies too. With Neon, keep sql but add and owner_id = ${user.id}, with the id taken from the session.
text
Audit every file with "use server" and every route handler under app/api.
In each exported function: read the session on the server, return 401 or throw
if there is none, and check that the signed-in user owns the record before
reading, changing or deleting it. Take the user id from the session, never from
the request body. Validate the input inside the function with zod. Do not rely
on middleware or on hidden buttons. List every function you changed.

Keep secrets on the server

text
List every environment variable this app reads and where. Only the Supabase URL
and the publishable key may use the NEXT_PUBLIC_ prefix. Move every use of
SUPABASE_SECRET_KEY, SUPABASE_JWT_SECRET, POSTGRES_URL, DATABASE_URL and AI
provider keys into Server Actions, route handlers or modules that start with
import "server-only". Never add NEXT_PUBLIC_ to fix an undefined value.
Tell me which variables were ever public so I can rotate them.

Lock the Supabase tables

sql
alter table public.tasks enable row level security;
revoke all on table public.tasks from anon, authenticated;
grant select, insert, update, delete on table public.tasks to authenticated;

create policy "tasks_select_own" on public.tasks
  for select to authenticated using ((select auth.uid()) = user_id);
create policy "tasks_insert_own" on public.tasks
  for insert to authenticated with check ((select auth.uid()) = user_id);
create policy "tasks_update_own" on public.tasks
  for update to authenticated
  using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "tasks_delete_own" on public.tasks
  for delete to authenticated using ((select auth.uid()) = user_id);
One table, one owner column. Ask v0 to write the same migration for every table in public, then re-run Security Advisor.

Protect AI routes and upgrade Next.js

text
Every route under app/api that calls an AI model must check the session and
return 401 without one, cap the prompt length, and limit requests per user.
Then upgrade next to 16.4.5 (or 15.5.29 if the app is on 15.5), update react
and react-dom to their latest patch release, and run a production build.

The traps after the first fix

  • Removing the prefix without rotating. Deleting NEXT_PUBLIC_ fixes the next build, not the key that already shipped. Rotate it at the provider, then update the variable in Vercel.
  • Signed in is not the owner. A check like if (!user) throw stops strangers only until they sign up. If sign-up is open, a stranger is one form away from passing it, so compare the record's owner with user.id.
  • Validation only in the form. A schema checked in a client component does nothing for a direct POST. Parse the input again inside the action.
  • HTML from the model. React escapes what it renders, except through dangerouslySetInnerHTML. Chat apps that render AI answers or user Markdown as HTML need a sanitizer.
  • No Content Security Policy. Next.js does not send one unless you configure it, and a CSP is what limits the damage when a script injection slips through. Add headers in next.config and run the security headers checker on your live address.
  • Previews on production data. Vercel sets each variable per environment: Production, Preview, Development. If previews point at the production database, a preview with protection off is a second public copy of your app on real data. Neon can create a separate database branch for each preview.

What Keybrum tests on a live v0 app

Reading code finds the missing check. An attack shows what a stranger can actually do with it. On a published v0 app whose owner proved ownership, Keybrum:

  • reads the JavaScript for secrets, from Supabase secret keys to AI, Stripe and database credentials, and checks whether each one works;
  • tries the Supabase tables, functions and buckets the app names, as anon and as a freshly signed-up account;
  • creates a marked record with one test account, then asks for it with a second account and with none, to prove an IDOR;
  • replays pages that redirect to sign-in with the CVE-2025-29927 header, as router prefetches and through path variants;
  • tests Server Actions for the Origin: null CSRF bypass (CVE-2026-27978) and the Host header SSRF (CVE-2024-34351), and reports the React Server Components patch level without attempting exploitation;
  • sends a five-word prompt to routes named like AI endpoints, and checks source maps, exposed .env files and security headers.

Every finding comes with the request that worked and the fix, which your coding agent reads through Keybrum's MCP server. Keybrum proves a CVE by its behaviour, never from a version number, and attacks again after your fix.

v0 security checklist

  • next is at 16.4.5 or later (or 15.5.29 on 15.5), and react is on its latest patch
  • Only the Supabase URL, the publishable key and truly public values use NEXT_PUBLIC_
  • SUPABASE_SECRET_KEY, SUPABASE_JWT_SECRET, POSTGRES_URL and DATABASE_URL appear only in server code
  • Keys are stored as Vercel Secret variables, and keys that were not marked sensitive in April 2026 have been rotated
  • Every secret that ever carried the prefix has been rotated
  • Every exported Server Action checks the session and the record's owner
  • Every route handler under app/api does the same, AI routes included
  • Pages behind middleware check the session again where they load data
  • Every Supabase table in public has RLS on, minimal grants and owner-based policies
  • Queries through POSTGRES_URL or Neon filter by the signed-in user's id
  • Input is validated again inside each action, not only in the form
  • AI providers have spend limits, and AI routes have per-user rate limits
  • Chats holding keys or customer data are private, and previews are protected
  • The live app was tested signed out and as a second account after the last deploy

For the same audit across every AI builder, see the vibe coding security checklist and the vibe coding security risks behind it.

Questions

Is v0 safe to use?

Yes, as a builder. v0 analyzes generated code before running it in sandboxes, and it is in the scope of Vercel's SOC 2 Type 2 attestation. The app it builds still needs authorization checks in its Server Actions, routes and database, and v0 does not verify those for you.

Is v0 generated code secure?

It follows Next.js conventions, which helps: React escapes what it renders, only NEXT_PUBLIC_ variables reach the browser, and v0 warns about risky prefixes. The usual gaps are actions and routes without an owner check, middleware used as the only lock, and Supabase tables without RLS. Read the server code before you publish.

Does Vercel protect my v0 app from Next.js CVEs?

Sometimes. Apps hosted on Vercel were not affected by CVE-2025-29927 because of how Vercel runs routing, and Vercel pushed React2Shell patches to v0 chats while telling owners to patch right away. Other CVEs need the upgrade in your package.json: the November 2026 security release is Next.js 16.4.5 or 15.5.29.

Can other people see my v0 chats?

Only if the visibility allows it. Anyone with the link opens the chat to whoever has the URL, and Anyone on the web may get it indexed by search engines. Team chats are open to the whole team by default, and Vercel team Owners can open any team chat by its URL.

Are environment variables in v0 safe?

Variables without the NEXT_PUBLIC_ prefix stay on the server, and variables with it are written into the JavaScript every visitor downloads. Store keys as Vercel Secret variables, which are write-only after saving: in April 2026 an attacker decrypted variables that a limited subset of Vercel customers had not marked sensitive. Read secrets only in Server Actions, route handlers or server-only modules.

Does v0 scan my app for vulnerabilities?

v0's documentation describes a security analysis of generated code before it runs and warnings about NEXT_PUBLIC_ usage. Neither sends requests to your live app the way a stranger would. The free check shows the outside view in about ten seconds.

Sources

  1. 1.Security, v0 Docs
  2. 2.Full-stack apps, v0 Docs
  3. 3.Databases, v0 Docs
  4. 4.Sharing, v0 Docs
  5. 5.Deployments, v0 Docs
  6. 6.FAQs, v0 Docs
  7. 7.Vercel April 2026 security incident, Vercel
  8. 8.React2Shell security bulletin, Vercel
  9. 9.Postmortem on Next.js Middleware bypass, Vercel
  10. 10.Config and Secret environment variables, Vercel Docs
  11. 11.Deployment Protection, Vercel Docs
  12. 12.How to think about data security in Next.js, Next.js Docs
  13. 13.November 2026 Security Release, Next.js Blog
  14. 14.CVE-2026-44575, NIST National Vulnerability Database
  15. 15.Middleware / Proxy bypass via segment-prefetch routes, incomplete fix follow-up (CVE-2026-45109), GitHub Advisory Database
  16. 16.Vercel Marketplace, Supabase Docs
  17. 17.Row Level Security, Supabase Docs
  18. 18.Vercel native integration, Neon Docs
  19. 19.Row Security Policies, PostgreSQL documentation

Your app is public. So are its mistakes.

Check what any attacker can already see in it, free, in ten seconds. The full attack, with proof and fixes: €49 once, or €29 a month on a plan.

or continue with Google or GitHub