Vercel
Exposed API Keys

Exposed API Keys on Vercel

Vercel provides a robust environment variable system, but the NEXT_PUBLIC_ prefix is the most common cause of secret exposure in Next.js deployments. Variables with this prefix are bundled into client-side JavaScript and visible to every visitor.

Scan Your Vercel App

How It Happens

Vercel's environment variable system has a critical distinction: variables prefixed with NEXT_PUBLIC_ are embedded in the client-side JavaScript bundle at build time. Variables without the prefix are available only on the server. Developers who do not understand this distinction expose secret keys by adding NEXT_PUBLIC_ to make their code "work" when a server-side variable is not accessible in a client component. This mistake is especially common when developers migrate from server components to client components. A server component that accesses process.env.STRIPE_SECRET_KEY works fine. When the same code is moved to a client component ('use client'), the variable becomes undefined. The quick fix is renaming it to NEXT_PUBLIC_STRIPE_SECRET_KEY, which makes it work but exposes the key to the browser. Vercel's preview deployments add another risk vector. Environment variables set for preview branches may differ from production, and developers sometimes use less restrictive values for testing. If preview deployment URLs are accessible (which they are by default), these test secrets are exposed.

Impact

Secret keys exposed through NEXT_PUBLIC_ variables are visible in the static JavaScript bundle served by Vercel's CDN. They are cached globally and persist even after the environment variable is corrected, until the site is redeployed and the CDN cache is purged. Stripe secret keys (sk_live_) allow attackers to view all customer data, process refunds, and create charges. Database connection strings give direct access to production data. AI service keys (OpenAI, Anthropic) result in financial loss through unauthorized usage. Because Vercel apps are served from a global CDN, exposed keys are available worldwide with low latency, making automated exploitation fast and efficient.

How to Detect

Open your Vercel deployment in a browser, view page source, and search for key prefixes: sk_, key_, mongodb+srv, postgres://. Check the JavaScript bundles in DevTools > Sources for any strings that look like API keys. In your project settings on Vercel, review all environment variables. Any variable with the NEXT_PUBLIC_ prefix should be safe to expose publicly. If it contains a secret, the prefix must be removed. Vibe App Scanner scans the deployed JavaScript bundle and HTML source for API keys, identifying both known key formats and suspicious high-entropy strings that may be exposed secrets.

How to Fix

Remove the NEXT_PUBLIC_ prefix from any environment variable that contains a secret. Access it only from server-side code: Server Components, API Routes, Server Actions, or middleware. For client-side features that need data from a secret-protected API, create a Server Action or API Route that acts as a proxy. The server-side code accesses the secret, calls the external API, and returns only the needed data to the client. Redeploy your application after removing exposed variables to ensure the CDN serves updated bundles without the secret keys. Use Vercel's CLI to purge the edge cache if needed. Rotate any key that was exposed in a previous deployment. Even if the current deployment is clean, previous builds with the exposed key may exist in browser caches, web archives, or CDN edge caches.

Code Examples

Environment variables in a Vercel Next.js project

Vulnerable
// .env on Vercel - secret exposed to browser!
NEXT_PUBLIC_STRIPE_SECRET=sk_live_abc123...

// Client component uses it directly
'use client'
const stripe = new Stripe(
  process.env.NEXT_PUBLIC_STRIPE_SECRET!
)
Secure
// .env on Vercel - server only (no NEXT_PUBLIC_ prefix)
STRIPE_SECRET_KEY=sk_live_abc123...

// Server Action proxies the request
'use server'
export async function createCheckout(priceId: string) {
  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
  const session = await stripe.checkout.sessions.create({
    line_items: [{ price: priceId, quantity: 1 }],
    mode: 'payment',
    success_url: `${process.env.NEXT_PUBLIC_URL}/success`,
  })
  return session.url
}

Frequently Asked Questions

Are NEXT_PUBLIC_ variables always a security risk?

No. NEXT_PUBLIC_ variables are designed for values that are safe to expose publicly, like Supabase anon keys, Firebase API keys, and analytics tracking IDs. The risk is when developers use this prefix for genuinely secret keys like Stripe secret keys or database passwords.

Does Vercel warn about exposed secrets?

Vercel does not automatically detect if a NEXT_PUBLIC_ variable contains a secret. It is the developer's responsibility to ensure only safe-to-expose values use this prefix.

Do preview deployments share the same environment variables?

Vercel allows different environment variable values for Production, Preview, and Development. Check your Vercel project settings to ensure preview deployments don't have sensitive values in NEXT_PUBLIC_ variables. Preview URLs are accessible by default.

Is Your App Vulnerable?

VAS automatically scans for exposed api keys and other security issues in Vercel apps. Get actionable results with step-by-step fixes.

Scans from $5, results in minutes.