Is your v0 app leaking?

v0 writes clean Next.js. The leaks come from the edges it does not own: what got a NEXT_PUBLIC_ prefix, what a route handler answers to, and which headers the deploy sends. Paste the address and check.

What a v0 app ships with

The five things that go wrong

  1. A secret with a NEXT_PUBLIC_ prefix. NEXT_PUBLIC_ is the switch that copies a value into the browser. A database URL, a service-role key, or an OpenAI key with that prefix is public the moment the build runs.
  2. Route handlers that echo any Origin. Copied CORS code that returns the request's Origin with credentials allowed lets any site call your API as the signed-in visitor.
  3. No security headers. Vercel does not add Content-Security-Policy, HSTS, or frame protection on its own; a headers() block in next.config.js does.
  4. Supabase or Neon straight from a client component. A query in a "use client" file runs in the browser with whatever key it was given. Row Level Security has to cover it, or the query has to move to a server component or route handler.
  5. Source maps uploaded by an error-tracking plugin. Sentry and friends upload maps for readable stack traces; the wrong setting also serves them to everyone.

How to fix them

Send security headers from next.config.js

module.exports = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          { key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
          { key: "X-Content-Type-Options", value: "nosniff" },
          { key: "X-Frame-Options", value: "DENY" },
          { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
        ],
      },
    ];
  },
};

Keep the key in a route handler, not the page

// app/api/ask/route.ts   (OPENAI_API_KEY without NEXT_PUBLIC_ stays on the server)
export async function POST(req: Request) {
  const r = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json" },
    body: await req.text(),
  });
  return new Response(await r.text(), { status: r.status, headers: { "Content-Type": "application/json" } });
}

Answer CORS from a fixed list

const allowed = new Set(["https://your-app.com"]);
export async function GET(req: Request) {
  const origin = req.headers.get("origin") || "";
  const headers = new Headers({ "Content-Type": "application/json" });
  if (allowed.has(origin)) {
    headers.set("Access-Control-Allow-Origin", origin);
    headers.set("Vary", "Origin");
  }
  return new Response(JSON.stringify({ ok: true }), { headers });
}

Every finding in a scan comes with the exact lines for your host and framework, opened for the one it detected.

Questions people ask

Which variables are public in Next.js?
Only the ones starting with NEXT_PUBLIC_. Everything else is readable only in server components, route handlers, and server actions. The scan reports NEXT_PUBLIC_ names that sound like secrets, names only.
Does the scanner see my server components?
No, and neither does anyone else. It sees the HTML they render and the JavaScript the browser loads, which is where leaks live.
Vercel already gives me HTTPS. What is missing?
The certificate, yes. The headers that tell browsers to insist on it, to refuse framing, and to limit where scripts load from are yours to add.

Check your v0 app now

Free, read-only, ten seconds. Then, if you want it watched: weekly re-scans, uptime and error alerts, code checks on the repo.

Scan your app free