Lancé la primera versión pública de GeminiOmni un miércoles por la tarde. Era una página de aterrizaje minimalista con una sola herramienta funcional — una versión temprana del editor de imágenes Nano Banana 2 — y un botón que decía "Pruébalo gratis, sin registro".
El sitio se publicó a las 14:42 hora de Berlín. A las 15:05, mi panel de facturación de Google Cloud estaba dando la alarma.
Este artículo es el desglose completo de lo que salió mal, cuánto costó y lo que cambié antes de que terminara el mismo día laboral. Si estás construyendo algo que toque una API de IA de pago, tómalo como una lección gratuita. La mayoría de la gente no escribe sobre las partes vergonzosas.
La secuencia real
Aquí está la cronología de mi propio registro de incidentes (sí, llevo uno, lo recomiendo):
- 14:42 Despliegue exitoso. Sitio en vivo en geminiomni-ai.com. Publiqué una demo rápida en una pequeña instancia de Mastodon y me fui a preparar un café.
- 14:58 Regresé a una notificación de Slack de Google Cloud: "Tu factura ha superado los $5 para el ciclo actual." Esto era alarmante porque el ciclo llevaba activo solo dieciséis minutos.
- 15:05 Accedí a la consola de la API de Gemini. Vi 412 solicitudes de generación de imágenes en los últimos 20 minutos, desde 38 direcciones IP distintas en tres continentes.
- 15:08 Desconecté el sitio. Eliminé la clave de API. Generé una nueva que no usaría todavía.
- 15:42 Empecé a reescribir la arquitectura.
- 18:30 Publiqué la v2 con la corrección. Volví a poner el sitio en línea.
- 23:00 Factura total por las cuatro horas de incidente: $84.30.
Para contexto, había presupuestado $200 para todo el primer mes de gasto en API. Quemé el 42% en diecisiete minutos por un error que debería haber sabido evitar.
Lo que había hecho
La primera versión del editor de imágenes estaba conectada así: una página de Next.js con un componente de React que tomaba el prompt del usuario y la imagen subida, luego llamaba directamente a la API de Google Gemini desde el navegador usando el cliente @google/genai y una clave de API incrustada en NEXT_PUBLIC_GEMINI_API_KEY.
Sí. Puse una clave de API de pago en NEXT_PUBLIC_*, que Next.js empaqueta obedientemente en el JavaScript del cliente, visible para cualquiera que abra DevTools.
En mi defensa — y esta defensa no es muy sólida — el prototipo original era un experimento privado en localhost. Configuré la clave como variable de entorno del cliente porque estaba iterando rápido y no quería molestarme en montar una ruta de API. Cuando preparé el despliegue público, cambié aproximadamente cuarenta cosas y la ubicación de la clave no fue una de ellas.
El error no fue exclusivo mío. Media docena de personas a las que respeto han hecho variaciones de esto en 2024-2025. Es el incidente de costos de IA prevenible más común que he visto.
Cómo la extrajeron tan rápido
El detalle que me sorprendió fue la rapidez con la que llegaron las solicitudes. Había publicado en una instancia de Mastodon con quizás 600 seguidores, ninguno de los cuales habría hecho esto maliciosamente. El tráfico tenía que venir de otro lado.
Esto es lo que deduje de los registros de solicitudes:
- A los cinco minutos de estar en línea, un rastreador automático visitó el sitio, extrajo la clave
NEXT_PUBLIC_GEMINI_API_KEYdel JavaScript empaquetado y la publicó en un canal de intercambio de claves que no sabía que existía. - En diez minutos, unas 38 direcciones IP diferentes estaban usando esa clave para generar imágenes sin relación con mi producto. La mayoría de los prompts que muestreé en los registros eran genéricos ("un auto deportivo rojo", "chica anime en un bosque"). Algunos probaban los límites del modelo con prompts adversariales.
- Al minuto quince, alguien estaba ejecutando un bucle cerrado generando imágenes tan rápido como Google lo permitía.
Esta es la parte que quiero que los desarrolladores independientes entiendan: existen rastreadores cuyo único trabajo es escanear sitios nuevos en busca de claves de API. Monitorean la emisión reciente de certificados SSL, visitan nuevos dominios, analizan los paquetes de JavaScript y publican las claves descubiertas en cuestión de minutos. No hace falta ser famoso. No hace falta estar en un subreddit popular. Solo hace falta estar en línea con una clave filtrada.
La arquitectura que publiqué esa tarde
La solución fue estructural, no cosmética. Reescribí el flujo de datos para que nunca se envíe una clave de API al navegador, y la arquitectura es la que usamos hoy:
Navegador (sin claves, nunca)
↓ POST /api/ai/generate { prompt, imageFile }
Ruta de API de Next.js (del lado del servidor)
↓ envConfigs.gemini_api_key (almacenada en el servidor)
API de Google GeminiTres cambios concretos:
1. Mover la clave de API a una variable de entorno solo del servidor. Renombré NEXT_PUBLIC_GEMINI_API_KEY a GEMINI_API_KEY. Next.js se negará a enviar variables de entorno solo del servidor al cliente por defecto. Por seguridad adicional, añadí una verificación al inicio que bloquea la aplicación si alguna variable de entorno cuyo nombre contenga "API_KEY" o "SECRET" comienza con NEXT_PUBLIC_.
2. Crear un proxy /api/ai/*. Cada llamada al modelo pasa por un único endpoint del lado del servidor. El navegador envía el prompt y los archivos subidos por el usuario; el servidor añade la clave de API, realiza la llamada real a la API de Gemini y devuelve solo el resultado. Suena obvio, pero es la solución completa.
3. Añadir un límite de tasa por IP en el proxy. Incluso con la clave segura, no quería que un usuario spammera generaciones de nivel gratuito para consumir créditos. El proxy aplica un límite de 10 solicitudes por IP por minuto en el nivel gratuito y 60 por minuto en Pro+. Esto no habría prevenido el incidente original —los atacantes rotaban IPs— pero hace que la siguiente clase de abuso sea mucho más difícil.
El cambio total de código fue de unas 180 líneas añadidas y 90 eliminadas. Me tomó 2 horas y 48 minutos desde "sitio desconectado" hasta "sitio en vivo de nuevo con nueva arquitectura". No está mal, pero no es catastrófico.
Qué haría diferente la próxima vez
La respuesta honesta es: nunca publiques una aplicación de IA pública con una llamada a API desde el cliente a un servicio de pago. Ni para "solo un prototipo", ni para "pruebas de MVP", ni para "una demo rápida para amigos". El modelo de amenaza no son los usuarios maliciosos, sino la infraestructura automatizada que ya existe exactamente para este escenario.
Algunas lecciones adicionales que surgieron del incidente:
Revisa el paquete antes de publicar. Antes de publicar, ejecuta grep -r "NEXT_PUBLIC_" .next/static/ y lee lo que hay dentro. Si algo parece una credencial, arréglalo antes de publicar. Ahora tengo esto como un hook de git previo al despliegue.
Establece un límite diario duro. Google Cloud te permite limitar tu API de Gemini a una cantidad diaria en dólares. Lo configuré en $50 para el primer mes y $200 después. Si se alcanza el límite, la API devuelve errores y recibo un correo electrónico, mucho mejor que dejarlo llegar a $5,000 en una sola mala noche.
Usa el modelo de menor coste que funcione. Estaba gastando $0.151 por edición 4K de Nano Banana 2 en un nivel gratuito. Incluso antes del ataque, mi economía unitaria era frágil. Ahora enruto las generaciones del nivel gratuito a través de Nano Banana 2K a $0.045 por edición y reservo 4K para Pro+. La reducción de coste de 3.4× habría hecho que la factura de $84 se acercara a $25.
Limita la longitud del prompt. Uno de los abusadores enviaba prompts masivos con datos de imagen incrustados, que Gemini factura por token. El proxy ahora tiene un límite fijo de tamaño de prompt entrante de 50 KB. Un usuario legítimo nunca lo ha superado; un abusador lo alcanza en la primera solicitud.
Los recibos
Para cualquiera que quiera verificar que no me lo invento: mi consola de la API de Gemini del 28 de abril muestra 411 solicitudes de 14:42 a 15:08, con el informe de gastos mostrando $83.96 en cargos de generación de imágenes y $0.34 en tokens de entrada. Me comuniqué con el soporte de Google Cloud esa noche y se negaron a reembolsar — justo, la clave era legítimamente mía y había autorizado su uso. Sugirieron exactamente la arquitectura que ya había implementado.
La conclusión clave, escrita lo suficientemente grande como para leerla desde el otro lado de la habitación:
Las claves de API de IA son credenciales de portador. Van en el servidor. Nunca van a ningún otro lado.
Si te llevas una sola cosa de este artículo, que sea esa.
Sigue leyendo
- Arquitectura completa del proxy del lado del servidor de GeminiOmni
- Por qué Veo 3.1 Fast supera a Sora para trabajo de video independiente
- Precios — cómo elegí $19 / $79
— Lena
