Vulnerability
Next.js

API Key Exposure in Next.js Applications

Next.js uses the NEXT_PUBLIC_ prefix to expose environment variables to the browser bundle. Developers frequently prefix secret API keys with NEXT_PUBLIC_ to use them in components, unknowingly embedding them in the client JavaScript for anyone to extract.

Scan Your Next.js App

How API Key Exposure Manifests in Next.js

API keys leak in Next.js through: Environment variables prefixed with NEXT_PUBLIC_ that contain secret keys (Stripe secret, OpenAI, database credentials). These are embedded in the JavaScript bundle at build time and visible to anyone. Server-side keys accidentally imported into client components. If a module with secrets is imported in a Client Component, the secret may be bundled. API routes that return secret keys in responses or include them in error messages. .env files committed to git repositories without .gitignore protection.

Real-World Impact

A Next.js SaaS app prefixed their OpenAI API key with NEXT_PUBLIC_ to call the API from client components. An attacker extracted the key from the JavaScript bundle and used it to generate millions of tokens, running up a $15,000 bill in a single weekend before the developer noticed.

Step-by-Step Fix

1

Move API calls to server-side routes

Instead of calling APIs from client components with exposed keys, create API routes.

// UNSAFE - key in client component
// .env
// NEXT_PUBLIC_OPENAI_KEY=sk-abc123

// SAFE - key only on server
// .env
// OPENAI_KEY=sk-abc123

// app/api/generate/route.ts
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_KEY, // No NEXT_PUBLIC_ prefix
});

export async function POST(req: Request) {
  const { prompt } = await req.json();
  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{ role: 'user', content: prompt }],
  });
  return Response.json(response.choices[0].message);
}
2

Use server-only package

Prevent server modules from being accidentally imported in client code.

// npm install server-only

// lib/api-keys.ts
import 'server-only';

export const OPENAI_KEY = process.env.OPENAI_KEY!;
export const STRIPE_SECRET = process.env.STRIPE_SECRET_KEY!;

// If a Client Component imports this module,
// the build will fail with a clear error.
3

Audit existing NEXT_PUBLIC_ variables

Check all NEXT_PUBLIC_ vars and move secrets to server-side only.

# Find all NEXT_PUBLIC_ usage
grep -r "NEXT_PUBLIC_" .env* --include=".env*"

# Check what should NOT be public:
# NEXT_PUBLIC_STRIPE_SECRET_KEY  -> STRIPE_SECRET_KEY
# NEXT_PUBLIC_OPENAI_KEY         -> OPENAI_KEY
# NEXT_PUBLIC_DATABASE_URL       -> DATABASE_URL

# These are OK as NEXT_PUBLIC_:
# NEXT_PUBLIC_SUPABASE_URL
# NEXT_PUBLIC_SUPABASE_ANON_KEY
# NEXT_PUBLIC_FIREBASE_API_KEY

Prevention Best Practices

1. Never prefix secret keys with NEXT_PUBLIC_. Use server-side API routes to proxy requests. 2. Only use NEXT_PUBLIC_ for truly public values (Firebase config, public Supabase anon key). 3. Add .env* to .gitignore and use .env.example for documentation. 4. Use server-only package to prevent server modules from being imported in client code. 5. Rotate any key that has been accidentally exposed.

How to Test

1. View your deployed site's JavaScript source and search for API key patterns (sk-, api_key, secret). 2. Check .env files for NEXT_PUBLIC_ prefixed secrets. 3. Search your git history for committed secrets: git log -p | grep -i "api_key\|secret\|password" 4. Check the built Next.js output (.next/static/) for embedded keys. 5. Use Vibe App Scanner to detect exposed API keys in your Next.js application.

Frequently Asked Questions

Are NEXT_PUBLIC_ variables always unsafe?

No. NEXT_PUBLIC_ is safe for truly public values like Firebase API keys, Supabase anon keys, and analytics IDs. It is unsafe for secret keys that grant write access or incur costs (Stripe secret, OpenAI, database URLs). The key distinction is whether the value is meant to be public.

Can I call external APIs from Next.js without exposing keys?

Yes. Create an API route (app/api/...) that makes the external API call server-side. The client calls your API route, which adds the secret key and forwards the request. The key never reaches the browser.

What should I do if I accidentally exposed an API key?

Immediately rotate the key through the provider's dashboard. Check git history and any cached builds for the old key. Use git filter-branch or BFG Repo Cleaner to remove the key from git history. Enable key restrictions where possible.

Is Your Next.js App Vulnerable to API Key Exposure?

VAS automatically scans for api key exposure vulnerabilities in Next.js applications and provides step-by-step remediation guidance with code examples.

Scans from $5, results in minutes. Get actionable fixes tailored to your Next.js stack.