Critical severity

How to fix a hardcoded API key in client-side JavaScript

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.

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.

How to check

  1. 01View your site source (Ctrl+U) and search for common patterns: `sk_`, `sk-`, `AKIA`, `xoxb-`, `ghp_`, `pk_live_`.
  2. 02Check DevTools → Sources for your bundle files and search the same patterns.
  3. 03Or download the JS with `curl https://your-site.com/_next/static/chunks/*.js` and grep.

Or let SafeToShip check it for you in 60 seconds:

How to fix it

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.

Lovable

Prompt Lovable: 'Move all third-party API calls to server-side functions. No API keys should exist in the frontend code. Rotate any exposed keys in the provider dashboard first.'

AI prompt

Copy-paste into your AI tool

Paste this prompt into Cursor, Lovable, Bolt, v0, or Claude Code and it will walk through the fix for your specific codebase.

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.

Scan your site for this and 50+ other issues

Free scan. Results in 60 seconds. No account required.