¿Clave API expuesta en el navegador? Comprueba tu clave de OpenAI, Claude o Gemini en 2 minutos
Si tu aplicación web llama a OpenAI, Claude o Gemini directamente desde el navegador, tu clave de API queda expuesta en el navegador, y cualquiera que abra DevTools puede copiarla. Puedes confirmarlo en 2 minutos: abre la pestaña Red, envía una solicitud de IA y busca un encabezado Authorization, x-api-key o x-goog-api-key que vaya al proveedor. Si ves tu clave ahí, rótala hoy y mueve la llamada detrás de una ruta de servidor.
A continuación: la comprobación, el orden de corrección y cómo tu agente de programación puede probar la ruta corregida.
Por qué una clave de API expuesta en el navegador es un problema
Una clave de API expuesta en el navegador es un secreto que se envía dentro del código de tu frontend o del tráfico de red, donde cada visitante puede leerlo. Si el navegador envía la clave, el usuario puede copiarla y llamar al proveedor a tu costa.
La guía de claves de API de Gemini de Google dice “No codifiques directamente las claves de API en aplicaciones web o móviles” porque “Las claves compiladas en código del lado del cliente pueden ser extraídas por los usuarios”, y te indica que “ejecutes un servidor proxy backend para hacer las llamadas reales a la API”. La guía de seguridad de claves de API de OpenAI da la misma regla: enruta las solicitudes a través de tu propio backend en lugar de desplegar la clave en el cliente.
El README de openai-node dice que el soporte para navegador está deshabilitado por defecto “para evitar exponer tus credenciales secretas de API”, y tienes que establecer dangerouslyAllowBrowser: true para activarlo. El SDK de TypeScript de Anthropic usa el mismo nombre de opción, y su API solo acepta llamadas de navegador (CORS) cuando la solicitud lleva el encabezado anthropic-dangerous-direct-browser-access: true, como documentó Simon Willison cuando se lanzó.
El patrón es común en aplicaciones creadas rápidamente con constructores de aplicaciones de IA y agentes de programación: el camino más corto que funciona es un fetch desde un componente de React con la clave en una variable VITE_ o NEXT_PUBLIC_. Las claves robadas luego se revenden para inferencia masiva, como se trata en nuestra publicación sobre claves de API de IA robadas y LLMjacking, y tu factura es donde lo notas.
Cómo comprobar si tu clave de API está expuesta (2 minutos)
Ejecuta estas comprobaciones contra producción, no contra localhost.
-
Observa la pestaña Red (45 segundos). Abre tu sitio, pulsa F12 (Cmd+Option+I en un Mac), elige la pestaña Red y activa una función de IA, como enviar un mensaje de chat. Filtra por
openai,anthropicogoogleapis. Si alguna solicitud va directamente aapi.openai.com,api.anthropic.comogenerativelanguage.googleapis.com, haz clic en ella y abre Encabezados. Una clave en[REDACTED CREDENTIAL],x-api-key,x-goog-api-keyo un parámetro de consulta?key=` significa que está expuesta. -
Busca en el bundle (45 segundos). En DevTools, pulsa Cmd+Option+F (Ctrl+Shift+F en Windows) para buscar en todos los scripts cargados. Busca
sk-,sk-ant-,AIzaydangerouslyAllowBrowser. Cualquier coincidencia en un archivo.jsservido al navegador es una fuga, incluso si aún no se ha lanzado ninguna solicitud. -
Revisa los prefijos de tus variables de entorno (30 segundos). Abre tu archivo
.envy el panel de tu proveedor de hosting. Las herramientas de compilación copian algunas variables en el bundle del cliente por diseño:VITE_*en Vite (consulta la documentación de entorno de Vite),NEXT_PUBLIC_*en Next.js,REACT_APP_*en Create React App yEXPO_PUBLIC_*en Expo. Una clave de proveedor detrás de cualquiera de esos prefijos es pública, sin importar cómo se llame.
Para un escaneo de terminal de la salida de tu build:
`bash
from your project root after a production build
grep -rnoE "sk-(proj-|ant-)?[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{35}" dist/ build/ .next/static/ 2>/dev/null
grep -rn "dangerouslyAllowBrowser" dist/ build/ .next/static/ 2>/dev/null
`
Una salida vacía es un aprobado. Google dice que las nuevas claves de AI Studio creadas desde el 28 de mayo de 2026 son “claves de autenticación”, cuyo formato puede diferir de AIza, así que busca también en el bundle generativelanguage.googleapis.com. Apruebas solo cuando el navegador habla con tu propio dominio (por ejemplo, /api/chat) y no aparece ningún nombre de host de proveedor ni clave en ningún sitio.
Qué hacer cuando tu clave está expuesta: orden de corrección
Rotar primero cierra el agujero de hoy; el cambio de arquitectura lo cierra para siempre.
| Paso | Qué haces | Por qué este orden | Tiempo |
|---|---|---|---|
| 1. Rota la clave | Crea una clave nueva en la consola del proveedor y luego revoca la filtrada | Puede que la clave antigua ya esté copiada; revocarla es lo único que detiene su uso | 5 min |
| 2. Mueve la llamada al lado del servidor | Pon la llamada al proveedor en una ruta de servidor o función edge que contenga la clave | Evita que la nueva clave se filtre de la misma manera | 30 a 60 min |
| 3. Añade autenticación y límites de tasa por usuario | Exige una sesión en la ruta y limita las solicitudes por usuario | Sin esto, tu proxy se convierte en un endpoint de IA gratuito para cualquiera | 30 min |
| 4. Configura límites de gasto del proveedor | Establece presupuestos o límites de gasto en la configuración de facturación de cada proveedor | Limita la peor factura posible si algo más se filtra | 10 min |
| 5. Escanea en CI | Haz fallar la compilación si aparece un patrón de clave en el bundle de salida | Evita que la próxima función escrita por el agente deshaga la corrección | 15 min |
Despliega la clave nueva en el servidor antes de revocar la antigua, o tu función de IA dejará de funcionar. Nuestra guía sobre mejores prácticas para la rotación de claves de API cubre la rotación sin tiempo de inactividad. En el paso 4, la guía de seguridad de claves de OpenAI recomienda límites de gasto con aplicación estricta, porque las alertas por sí solas no detienen el tráfico; un presupuesto de facturación de Google Cloud te alerta pero no detiene el uso por sí mismo.
Cómo mover las llamadas de OpenAI, Claude y Gemini detrás de una ruta de servidor
Una ruta proxy backend es un pequeño endpoint de servidor que recibe el prompt del usuario, añade la clave de API en el servidor, llama al proveedor y devuelve solo la respuesta. El navegador nunca ve la clave. Aquí tienes una ruta mínima de Next.js para OpenAI; Claude y Gemini siguen la misma forma:
`ts
// app/api/chat/route.ts (runs on the server only)
import OpenAI from "openai";
import { getSession } from "@/lib/auth"; // your auth helper
import { checkRateLimit } from "@/lib/limits"; // your per-user limiter
const client = new OpenAI({ [REDACTED CREDENTIAL] }); // no NEXT_PUBLIC_ prefix
export async function POST(req: Request) {
const session = await getSession(req);
if (!session) return new Response("Unauthorized", { status: 401 });
if (!(await checkRateLimit(session.userId, 20))) { // 20 requests per minute
return new Response("Too many requests", { status: 429 });
}
const { message } = await req.json();
if (typeof message !== "string" || message.length > 4000) {
return new Response("Bad request", { status: 400 });
}
const response = await client.responses.create({
model: process.env.OPENAI_MODEL!, // pin the model server-side
input: message,
});
return Response.json({ reply: response.output_text }); // return the answer, nothing else
}
`
Qué lo hace seguro, no solo oculto:
- El modelo vive en el servidor. Si el navegador elige el modelo, un atacante elige el más caro.
- La entrada está limitada. No pasan prompts enormes por tu cuenta.
- Solo vuelve la respuesta. Nunca devuelvas la respuesta sin procesar del proveedor ni el objeto de error.
-
Sin prefijo público.
OPENAI_API_KEYpermanece en el servidor;NEXT_PUBLIC_OPENAI_API_KEYse envía a cada visitante.
Para Claude y Gemini, usa sus SDK con ANTHROPIC_API_KEY o GEMINI_API_KEY. Las restricciones de sitio web e IP de Google ayudan, pero cualquiera con curl puede enviar cualquier encabezado Referer, así que trátalas como una segunda capa. El único caso justo para llamadas desde el navegador es una herramienta de tipo trae tu propia clave donde cada usuario pega su propia clave.
Deja que tu agente de programación pruebe la ruta proxy con Apidog CLI
Mover la clave es la mitad del trabajo; la otra mitad es demostrar que sigue movida después de la próxima función que escriba tu agente. Deja que el agente que creó la ruta la pruebe con Apidog CLI. Configúralo una vez:
bash
npm install -g apidog-cli@latest
apidog login --with-token <YOUR_TOKEN>
npx -y skills add https://apidog.com # pick your agent when prompted
Luego dale a Claude Code, Codex, Cursor o el agente que uses un [REDACTED PROMPT]
Create Apidog test cases in project for POST /api/chat on our staging
environment that assert:
- a request without a session cookie returns 401,
- a valid request returns 200 and the body does not contain "sk-", "sk-ant-", "AIza" or "api_key",
- the 21st request inside 60 seconds from the same user returns 429.
Run them and summarize any failures.
`shell
El agente lee el esquema, valida su payload, escribe los casos y los ejecuta:
`bash
apidog test-case category --project <id>
apidog cli-schema get test-case-create
apidog cli-schema validate test-case-create --file case.json
apidog test-case create --project <id> --file case.json
apidog run --test-case <caseId> --project <id> -e <envId> -r cli,json
`
La segunda aserción es la que más importa: falla en el momento en que un objeto de error del proveedor o un campo de depuración empieza a devolver tu clave al cliente. Pon el mismo paso apidog run ... -r junit en tu pipeline para que cada despliegue lo vuelva a comprobar; nuestra guía de Apidog CLI para CI/CD muestra la configuración, y Apidog CLI en Claude Code cubre el lado del agente. El plan gratuito de Apidog cubre hasta 4 usuarios, así que un equipo pequeño puede ejecutar esto sin una licencia de pago.
Para el lado del repositorio del mismo problema, claves confirmadas en Git en lugar de enviadas al navegador, consulta nuestra comprobación de fuga de claves de API con un escáner de secretos. Si tus agentes llaman a herramientas que manejan datos de clientes, probar agentes de IA en busca de fugas de datos cubre la siguiente capa.
Errores comunes que mantienen la clave expuesta
- Codificar la clave en Base64 o dividirla. El navegador debe reconstruirla para enviarla, así que DevTools muestra el valor real.
- Moverla a localStorage o a una cookie. Sigue estando en el cliente.
-
Hacer proxy sin autenticación. Un
/api/chatpúblico es un endpoint de IA abierto a tu costa. - Conservar la clave antigua “por si acaso”. Una clave que fue pública durante un día está copiada.
- Olvidar la aplicación móvil. La guía de Google menciona “aplicaciones web o móviles” juntas por una razón.
Preguntas frecuentes
¿Cómo sé si mi clave de API de OpenAI está expuesta en el navegador? Activa una función de IA con la pestaña Red de DevTools abierta. Una solicitud a api.openai.com con un encabezado Authorization: Bearer [REDACTED]-... significa que está expuesta. Busca también en los scripts cargados sk- y dangerouslyAllowBrowser.
¿Es seguro poner una clave de API en una variable de entorno NEXT_PUBLIC_ o VITE_? No. Esos prefijos existen para copiar valores en el bundle del cliente. Cualquier clave de proveedor almacenada bajo ellos se envía a cada visitante. Usa una variable sin prefijo solo para servidor y léela dentro de una ruta de servidor.
¿Restringir una clave de API de Gemini a mi sitio web la hace segura en el navegador? Ayuda, pero un script fuera del navegador puede enviar cualquier encabezado Referer. La propia guía de Google recomienda un proxy backend para aplicaciones cliente.
Encontré mi clave expuesta. ¿Qué debo hacer primero? Crea una clave nueva, despliégala en el servidor y luego revoca la antigua. Después, mueve la llamada detrás de una ruta autenticada con límites de tasa por usuario, y configura límites de gasto del proveedor.
¿Puede mi agente de programación corregir y probar esto por mí? Sí. Pídele que mueva la llamada al proveedor a una ruta de servidor, luego usa Apidog CLI para crear y ejecutar pruebas que verifiquen un 401 sin sesión, que no haya patrones de clave en las respuestas y un 429 al superar tu límite de tasa.


Top comments (0)