HOW TO SECURE V0.DEV - SECURITY GUIDE | VIBEEVAL
v0.dev Security Context
v0 generates Next.js + shadcn/ui projects with Vercel as the deploy target. The frontend code is generally clean — shadcn primitives are well-built. The risks land at the seams: API route handlers in app/api/ that ship without auth checks, dangerouslySetInnerHTML for AI-generated content, and the Vercel deployment URL that bypasses any custom-domain auth gate.
Security Checklist
1. Audit every app/api/ route for auth
Open every file in app/api/**/route.ts. Each handler must check the session before doing work:
import { auth } from "@/auth"
export async function POST(req: Request) {
const session = await auth()
if (!session?.user) return new Response("Unauthorized", { status: 401 })
// ...
}
v0 routinely generates the route file without the check. Add it explicitly to every protected route — there is no implicit middleware.
2. Search for dangerouslySetInnerHTML
grep -r "dangerouslySetInnerHTML" app/ components/. Every match needs review — if the value is user-controlled or AI-generated, sanitize with DOMPurify before rendering. The bug shape is “render markdown the LLM produced,” which is also where LLM-rendered HTML/Markdown attacks land.
3. Move every API key to a server-only env var
In Next.js, only variables prefixed NEXT_PUBLIC_ are exposed to the browser. The Stripe secret key, OpenAI key, database URL — all unprefixed. Audit .env.local, .env.example, and every process.env.* reference. If process.env.OPENAI_API_KEY appears in a 'use client' file, it’s broken: client components can only read NEXT_PUBLIC_*.
4. Disable production source maps
In next.config.js: productionBrowserSourceMaps: false (the default since Next 13, but verify). Source maps shipped to production hand an attacker your unminified code, including comments — every implementation detail and TODO becomes recon. Verify by hitting /_next/static/chunks/*.js.map on production; should return 404.
5. Set security headers in next.config.js
Add HSTS, CSP, frame options:
async headers() {
return [{
source: "/:path*",
headers: [
{ key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }
]
}]
}
Then verify with the Security Headers Checker.
6. Lock down the preview-deployment URL
Vercel gives every push a <branch>-<hash>.vercel.app URL. If your auth gate is on the custom domain only, the preview URL bypasses it. Either: enable Vercel Deployment Protection (Pro plan), or check request.headers.get('host') in middleware and reject anything that isn’t your production domain.
7. Validate user inputs with Zod
In every API route, validate the request body before using it:
import { z } from "zod"
const Schema = z.object({ name: z.string().min(1).max(100) })
const body = Schema.parse(await req.json()) // throws on invalid
The default v0-generated route trusts await req.json() directly, which means any client can send any field of any size.
8. Configure CORS only where needed
If your API is meant for your own frontend only, don’t add CORS at all — same-origin works without headers. If you need cross-origin, never use '*' with credentials. See CORS credentials misconfig.
9. Strip sensitive data from event handlers
onClick handlers that POST to internal endpoints sometimes include the user’s full session token in the payload “to be safe.” That token now lives in your access logs. Pass IDs, never tokens — the session cookie does the auth.
10. Audit state for sensitive data
Use React DevTools on production to inspect component props and state. Look for password fields that haven’t been cleared, full user objects (with hashes) in client state, and tokens in useState. Move secrets to httpOnly cookies and never to client state.
11. Enable React strict mode
In next.config.js: reactStrictMode: true. Catches the double-effect bugs that turn into double-charges in payment flows. See race conditions in money paths for the underlying class.
12. Test error boundaries
Throw an intentional error in a server component: the production error page should say “Something went wrong” and a request ID — never the actual error message or stack. v0 sometimes generates error boundaries that render error.message directly, which leaks internal state.
13. Audit localStorage usage
grep -r "localStorage" app/ components/. Anything sensitive here (tokens, PII, draft data with secrets) is exposed to any XSS on the page. Use httpOnly cookies for tokens, server-side persistence for drafts.
14. Audit shadcn component overrides
If you customized a shadcn component to render arbitrary HTML or to skip a built-in escape, review carefully. The shadcn defaults are safe; custom overrides are where the XSS lands.
15. Run npm audit
After every dependency add. shadcn pulls in radix-ui, tailwind, and a fairly stable set — the CVE risk is usually low, but verify before each deploy.
16. Run a full security scan
The Vibe Code Scanner covers the deploy-side patterns: source maps, exposed Vercel URLs, .env.example with real keys, debug routes.
Related Resources
Is v0.dev Safe?
Security profile of v0 in the broader vibe-coding context.
Free Self-Audit Suite
Five free scanners.
Vibe Coding Security Risk Guide
Full risk catalogue for AI-built apps.
Automate Your Security Checks
VibeEval scans your v0-deployed Vercel app against every category above plus the long tail (BOLA, prompt injection on AI-rendered content, webhook trust, dependency CVEs).
SCAN YOUR APP
14-day trial. No card. Results in under 60 seconds.