Critical severity · Next.js

Hardcoded API key in JS on Next.js

An API key is hardcoded in your client-side JavaScript. Anyone who views your site source can extract it. The fix has three steps: (1) rotate the key immediately — assume it is compromised; (2) move the API call to your server (API route, server action, or serverless function) so the key never ships to the browser; (3) add guardrails — prefix only server secrets without NEXT_PUBLIC_, scan builds for leaked keys. Do not try to "obfuscate" the key — it will be found.

The fix for Next.js

Next.js — move to server

Create a Route Handler that calls the third-party API with the key. Your client calls your handler, never the third party directly.

// app/api/ai/route.ts — SERVER ONLY
import { NextRequest } from 'next/server';

export async function POST(req: NextRequest) {
  const { prompt } = await req.json();
  const res = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,  // no NEXT_PUBLIC_
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: prompt }] }),
  });
  const data = await res.json();
  return Response.json(data);
}

// Client calls /api/ai, never api.openai.com directly.

Why it matters

Attackers run automated bundle-scanning tools against popular sites. An exposed OpenAI, Stripe, or cloud key is drained within hours of publication — sometimes minutes. Your bill can run to thousands of dollars before you notice.

Confirm the fix worked

Scan your Next.js site to confirm this finding is gone.

AI prompt

Apply across your codebase

Paste this into Cursor, Lovable, Bolt, v0, or Claude Code.

Find every API key in my client-side code. For each one: (1) tell me which service it belongs to so I can rotate it; (2) determine if the API call needs to happen in the browser (it usually does not); (3) move the call to a server-side route handler or server action that reads the key from an env var without NEXT_PUBLIC_ prefix; (4) update the frontend to call my endpoint instead. Provide the rotation URL for each service.

FAQ

Frequently asked questions

But the user needs to make the API call from their browser for latency.
Then use a signed, time-limited credential (like S3 presigned URLs, Twilio Access Tokens, or OpenAI's ephemeral keys) generated server-side per-request. Never the raw master key.
My framework variables all start with NEXT_PUBLIC_. Is that fine?
For public values yes (Stripe publishable key, Supabase anon key). For secret values no — `NEXT_PUBLIC_` inlines the value into the browser bundle. Use `NEXT_PUBLIC_` only for values that are meant to be public.