AI API key is not accessible client-side
Why it matters
An AI API key accessible from client-side code is not a secret—it is a public credential embedded in a bundle downloaded by every browser that visits your site. CWE-312 (cleartext credential storage) and CWE-798 (hardcoded credential) both apply. OWASP A02:2021 (Cryptographic Failures) covers this class of exposure. Any attacker can extract the key from browser DevTools in under 30 seconds and use it to: exhaust your API quota (generating costs you bear), extract your system prompt by calling the provider directly, or abuse the provider's API for their own applications at your expense. In Next.js, prefixing any environment variable with NEXT_PUBLIC_ embeds its value in the client bundle at build time—this is the most common accidental exposure vector in production deployments.
Severity rationale
High because client-side key exposure requires no attack sophistication—the credential is readable in plain text from browser DevTools, making exploitation trivial and immediate once discovered.
Remediation
All AI API calls must be proxied through server-side routes. The AI SDK is never imported in client components, and the API key environment variable never carries a NEXT_PUBLIC_ prefix.
// Correct: server-side only (src/app/api/chat/route.ts)
import OpenAI from 'openai'
// Note: no NEXT_PUBLIC_ prefix — this variable is never in the client bundle
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
export async function POST(req: Request) {
// All AI calls happen here, server-side
}
// Client component (src/components/ChatWidget.tsx)
// No OpenAI import — just a fetch to your own API route
async function sendMessage(message: string) {
return fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message })
})
}
If you have an existing NEXT_PUBLIC_OPENAI_API_KEY in .env or .env.example, rotate the key immediately before removing the prefix—it should be treated as compromised.
Detection
-
ID:
api-key-server-only -
Severity:
high -
What to look for: List all LLM API key references across the codebase. For each, check where AI API calls are made. Look for AI SDK imports in client-side files (components, browser-side utilities, files without server-only markers). Check whether the AI API key environment variable is referenced in files that could be bundled into the client. In Next.js, look for
NEXT_PUBLIC_prefix on AI API key environment variables (this exposes them to the browser). Checklayout.tsx, client components ('use client'), and browser-executed scripts for AI provider client initialization. -
Pass criteria: All AI API calls are made server-side only (in API route handlers, server actions, server components, or backend services). The AI API key is accessed only from server-side code and is not prefixed with
NEXT_PUBLIC_or equivalent client-exposure prefix — 100% of LLM API keys must be server-side only, never in client bundles. Report: "X API key references found, all Y server-side only." -
Fail criteria: AI SDK is initialized or called in client-side code, OR the AI API key environment variable has a
NEXT_PUBLIC_prefix (or equivalent), OR the API key is hardcoded in any file. -
Skip (N/A) when: No AI provider integration detected.
-
Cross-reference: The Security Hardening audit
secrets-not-committedcheck covers broader secrets management. -
Detail on fail:
"OpenAI client initialized in components/ChatWidget.tsx (a client component) — API key is accessible in browser"or"NEXT_PUBLIC_OPENAI_API_KEY used in .env.example — this exposes the key to all browser users" -
Remediation: Exposing your AI API key client-side means anyone can use your API quota. All AI calls must be proxied through your server:
// Correct: server-side only (app/api/chat/route.ts) import OpenAI from 'openai' const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }) // no NEXT_PUBLIC_ // Client component just calls your API route // fetch('/api/chat', { method: 'POST', body: JSON.stringify({ message }) })For a broader analysis of secrets management and API key hygiene, the Security Headers & Basics Audit covers this in the Basic Hygiene category.
External references
- cwe · CWE-312 — Cleartext Storage of Sensitive Information
- cwe · CWE-798 — Use of Hard-coded Credentials
- owasp:2021 · A02 — Cryptographic Failures
- nist-ai-rmf:1.0 · GOVERN 1.1 — Policies, processes, procedures, and practices across organization to manage AI risks
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ai-prompt-injection·automated