Volver al blog
13 de mayo de 2026

API Key de OpenAI en Encabezados de Respuesta HTTP: Encontrada en 7 Minutos

Viktor Bulanek
Founder & CTO, Penetrify
MSc IT Security · 20+ years in security · 4x Ex-CTO

El panel de facturación de OpenAI mostraba cargos que el fundador no podía explicar. La aplicación tenía aproximadamente 800 usuarios en un modelo freemium, pero el uso de la API tendía a los $2,000 al mes, mucho más alto de lo que justificaba la actividad de los usuarios en la plataforma. El fundador asumió que tenían un prompt ineficiente en algún lugar y tomó nota para investigar.

Siete minutos después de iniciar un escaneo de Penetrify, la razón se hizo evidente: la clave de la API de OpenAI estaba siendo devuelta a los usuarios en los encabezados de respuesta HTTP de cada llamada a la API proxy. 800 usuarios la habían visto. Algunos de ellos la estaban usando.


La Arquitectura: De Dónde Provino la Fuga

La aplicación era un backend de FastAPI que servía un frontend de React. Su funcionalidad principal era actuar como proxy para las solicitudes de los usuarios a la API de OpenAI, añadiendo prompts de sistema personalizados, almacenando el historial de conversaciones y aplicando la capa de ingeniería de prompts propietaria del fundador. Este es un patrón común para los productos de envoltura de IA: el valor no es el modelo, es el producto construido a su alrededor.

Así funcionaba la aplicación:

  1. El usuario envía un prompt desde el frontend de React
  2. El frontend lo envía a POST /api/generate
  3. El manejador de FastAPI añade el prompt del sistema y llama a la API de OpenAI
  4. FastAPI devuelve la finalización al frontend

En algún lugar de la implementación de la ruta de FastAPI, el encabezado Authorization de la solicitud saliente de OpenAI —que contenía la clave de la API en formato de token Bearer— estaba siendo reenviado en la respuesta. Esta es una clase específica de error de reenvío de encabezados: la aplicación estaba pasando los encabezados de respuesta de la llamada a la API de OpenAI ascendente en lugar de construir sus propios encabezados de respuesta.

Los encabezados de respuesta en cada llamada a /api/generate incluían:

HTTP/1.1 200 OK
Content-Type: application/json
Authorization: Bearer sk-proj-...[OpenAI API key]
...
{"completion": "..."}

Cada usuario que había utilizado la función de generación —los 800— había recibido la clave de la API en los encabezados de respuesta de sus solicitudes. Era visible en la pestaña Red de las DevTools del navegador, en cualquier proxy HTTP y en cualquier cliente programático que leyera los encabezados de respuesta.


Qué te Ofrece una Clave de la API de OpenAI

Una clave de la API de OpenAI sin restricciones de uso otorga al titular acceso completo a la cuota de la API de la cuenta correspondiente. Esto significa:

  • Acceso ilimitado a modelos a expensas del propietario de la clave — GPT-4o, o1, o3, generación de imágenes, embeddings, fine-tuning
  • Sin límite por solicitud hasta que se alcance el límite de gasto mensual de la cuenta
  • Acceso a cualquier modelo fine-tuned que la cuenta haya creado
  • Capacidad para leer archivos almacenados si la cuenta utiliza la API de Archivos

Para un fundador individual cuya aplicación procesa $200–400/mes en uso legítimo, que su clave sea utilizada indebidamente externamente puede elevar la factura mensual a $2,000, $5,000 o más, dependiendo de cuán ampliamente circule la clave y qué estén generando los abusadores.

El modelo de costos para el abuso de la API de OpenAI es asimétrico: el atacante no paga nada, el propietario de la clave paga por todo.


Los Picos de Facturación Inexplicables, Explicados

Una vez identificada la exposición de la clave, los picos de facturación tuvieron sentido. El fundador consultó el panel de uso de OpenAI filtrado por endpoint y tiempo. El patrón de picos mostró solicitudes de alto volumen que no se correlacionaban con la actividad de los usuarios en la plataforma: solicitudes a las 3 de la mañana, solicitudes de rangos de IP que no coincidían con ninguna geografía de usuario conocida, solicitudes de tipos de modelos que la aplicación no utilizaba.

Alguien había extraído la clave — posiblemente varias personas — y la estaba utilizando directamente contra la API de OpenAI, saltándose la aplicación por completo. Las solicitudes iban directamente a OpenAI utilizando las credenciales extraídas, no a través de la aplicación del fundador.

La clave había estado expuesta desde aproximadamente la primera semana del lanzamiento público de la aplicación. En el momento del escaneo, había estado activa y filtrándose durante varios meses.


Otros Hallazgos

La exposición de la clave de OpenAI fue el hallazgo más inmediatamente perjudicial, pero se informaron tres problemas adicionales:

MEDIO — IDOR en /api/history/:userId

La aplicación almacenaba el historial de conversaciones por usuario y lo exponía en un endpoint predecible:

GET /api/history/abc123

El manejador de ruta obtenía el historial de conversaciones para el ID de usuario en el parámetro de ruta sin verificar si el usuario solicitante era propietario de esos registros. Cualquier usuario autenticado podía leer el historial de conversaciones de cualquier otro usuario sustituyendo su ID. Dado que las conversaciones incluían prompts proporcionados por el usuario, esto también era una exposición de privacidad: un atacante podía leer qué preguntas otros usuarios habían estado haciendo a la herramienta de IA.

MEDIO — Modo de depuración de FastAPI habilitado en producción

La aplicación se estaba ejecutando con FastAPI(debug=True). En modo de depuración, cualquier excepción no manejada devuelve un rastreo de pila completo en la respuesta HTTP, incluyendo rutas de archivos internas, versiones de dependencias y nombres de variables de entorno (aunque no sus valores). Esta información es directamente útil para planificar ataques adicionales — conocer la versión exacta de FastAPI, la versión de Pydantic y la versión de Python reduce significativamente la lista de CVEs aplicables.

El modo de depuración también habilita la documentación interactiva de FastAPI en /docs y /redoc por defecto, la cual era accesible en producción y documentaba cada endpoint interno de la API, incluyendo aquellos no destinados al acceso de usuarios.

BAJO — HTTP no redirigiendo a HTTPS

La versión HTTP de la aplicación servía contenido completo sin redirigir a HTTPS. En redes públicas o compartidas, un atacante realizando un ataque man-in-the-middle podría interceptar sesiones no cifradas y extraer tokens de sesión, prompts enviados por el usuario y respuestas de la API.


La Solución: Implementada la Misma Noche

El fundador implementó soluciones para todos los hallazgos dentro de las tres horas de recibir el informe.

Rotar la clave primero

Antes de tocar cualquier código, la acción inmediata fue revocar la clave comprometida en el panel de control de OpenAI y generar una nueva. Esto detuvo instantáneamente cualquier abuso en curso. La rotación de claves de OpenAI es inmediata — la clave antigua deja de funcionar en el momento en que la eliminas.

Corregir el error de reenvío de encabezados

La causa raíz fue que la ruta de FastAPI estaba utilizando un cliente HTTP genérico que reenviaba todos los encabezados de respuesta de la llamada upstream de OpenAI. La solución fue construir encabezados de respuesta explícitos en lugar de pasar los de upstream:

# Antes (vulnerable) — reenviando todos los encabezados upstream
upstream_response = await client.post(
    "https://api.openai.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {settings.OPENAI_API_KEY}", ...},
    json=payload
)
return Response(
    content=upstream_response.content,
    headers=dict(upstream_response.headers)  # ← esto reenvía el encabezado Authorization de vuelta
)

# Después (corregido) — construcción explícita de la respuesta
upstream_response = await client.post(
    "https://api.openai.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {settings.OPENAI_API_KEY}", ...},
    json=payload
)
completion_data = upstream_response.json()
return JSONResponse(content={"completion": completion_data["choices"][0]["message"]["content"]})
# Solo los datos que queremos devolver explícitamente — no se reenvían encabezados upstream

Corregir el IDOR

El endpoint del historial de conversaciones se actualizó para extraer el ID de usuario del JWT verificado en lugar de hacerlo del parámetro de ruta:

@router.get("/api/history")
async def get_history(current_user: User = Depends(get_current_user)):
    # El ID de usuario proviene del JWT verificado — no puede ser suplantado
    history = await db.get_history(user_id=current_user.id)
    return history

Desactivar el modo de depuración

# En config.py
app = FastAPI(
    debug=settings.DEBUG,  # lee de la variable de entorno
    docs_url=None if not settings.DEBUG else "/docs",  # oculta la documentación en producción
    redoc_url=None if not settings.DEBUG else "/redoc"
)

Con DEBUG=false configurado en el entorno de producción, la documentación interactiva y las respuestas de error detalladas desaparecieron inmediatamente en el siguiente despliegue.


Añadir límites de uso de OpenAI como red de seguridad

Además de solucionar la fuga, el fundador añadió dos medidas defensivas para limitar el radio de impacto de cualquier futura exposición de claves:

Límites de uso: En el panel de control de OpenAI, bajo Facturación → Límites de uso, establece un límite máximo mensual y un umbral de notificación suave. Incluso si una clave se ve comprometida de nuevo, la capacidad del atacante para acumular cargos está limitada.

Claves dedicadas por servicio: Crea una clave API separada para cada aplicación o entorno. Si una clave se ve comprometida, puedes rotar solo esa clave sin interrumpir otros servicios, y los registros de uso de cada clave están claramente separados, lo que facilita mucho la detección de accesos no autorizados.


¿Qué tan común es esto?

La exposición de claves API en respuestas HTTP es menos común que la exposición en paquetes JavaScript, pero la vemos regularmente específicamente en aplicaciones envoltorio de IA. El patrón casi siempre tiene la misma causa raíz: un desarrollador que construye una capa de proxy utiliza un cliente HTTP genérico que reenvía los encabezados de respuesta, y no audita lo que contienen esos encabezados.

El error de reenvío de encabezados es fácil de cometer porque a menudo simplifica la implementación. ¿Por qué construir una nueva respuesta cuando puedes reenviar la de origen? La respuesta, en este caso, es que la respuesta de origen contiene credenciales que no quieres compartir con tus usuarios.

Si tu aplicación actúa como proxy para llamadas a OpenAI, Anthropic o cualquier otra API externa, audita explícitamente tus encabezados de respuesta. Utiliza una herramienta como curl -v o las Herramientas de desarrollo de tu navegador para examinar cada encabezado devuelto por cada endpoint de la API. Los encabezados son fáciles de pasar por alto precisamente porque la mayoría de las veces son poco interesantes, lo que los convierte en un escondite tan eficaz para una fuga.


El contexto de la aplicación YC

El fundador estaba preparando una solicitud de YC en el momento del escaneo. La combinación de picos de facturación inexplicables, una clave API expuesta y una vulnerabilidad IDOR que afectaba al historial de conversaciones de todos los usuarios habría sido un problema significativo para explicar a los inversores —o, peor aún, para descubrir después de la financiación.

Los problemas de seguridad en la etapa de prelanzamiento o de tracción inicial son solucionables en horas. Los mismos problemas descubiertos después de un incidente de seguridad, una notificación de violación de datos o una historia mediática hostil tardan meses en recuperarse y pueden acabar con una empresa que aún no ha construido la buena voluntad para sobrevivir al ciclo de noticias.

El fundador ejecutó Penetrify de nuevo antes de enviar la solicitud de YC. El informe resultó limpio.

Frequently Asked Questions

¿Qué tipos de vulnerabilidades detecta Penetrify?

Penetrify detecta todas las categorías de vulnerabilidades del OWASP Top 10, incluyendo inyección SQL, XSS, CSRF, IDOR, autenticación rota, configuraciones de seguridad incorrectas y exposición de datos sensibles. También prueba la seguridad de APIs, la gestión de sesiones y configuraciones incorrectas comunes en Supabase, Firebase y Bubble.

¿Cuánto tiempo dura un test de penetración con IA?

Un escaneo rápido se completa en 15–30 minutos. Un escaneo estándar dura 1–2 horas con mayor cobertura. Un escaneo profundo puede durar varias horas en aplicaciones complejas.

¿Qué incluye un informe de Penetrify?

Cada informe incluye un resumen ejecutivo, una puntuación general de seguridad, hallazgos clasificados por severidad (Crítico, Alto, Medio, Bajo), pasos de reproducción detallados y orientación concreta de remediación escrita para desarrolladores, no para responsables de cumplimiento.

Related articles

CI/CD Penetration Testing: Cómo integrar la seguridad en cada despliegue
Aprenda cómo integrar Penetration Testing en su pipeline de CI/CD. Cubre SAST, DAST, puertas de calidad y pruebas impulsadas por IA sin ralentizar la entrega.
Análisis Autónomo de Vulnerabilidades OWASP: Cómo la IA Reemplaza las Pruebas de Seguridad Basadas en Reglas
Aprenda cómo el escaneo autónomo de vulnerabilidades OWASP utiliza IA para ir más allá de la coincidencia de firmas. Cubre el OWASP Top 10 2025, las pruebas agénticas y por qué los escáneres basados en reglas no son suficientes.
Simulación de Cadenas de Ataque de Múltiples Pasos: Por Qué el Escaneo de una Sola Vulnerabilidad No es Suficiente
Aprenda cómo la simulación de cadenas de ataque de múltiples pasos encuentra los exploits encadenados que los escáneres de vulnerabilidades pasan por alto. Ejemplos reales, mapeo de MITRE ATT&CK y guía de implementación.

Explore more

Compare alternatives →Security glossary →CI/CD integration →Security statistics →
Volver al blog