Fundamentos de seguridad
Tu clave de API de OpenAI está expuesta en tu frontend. Rótala.
Una clave de API de OpenAI expuesta en tu frontend no se puede atar a un dominio. Rótala hoy, mueve la llamada a tu servidor y limita el gasto.

En resumen
- Una clave de API de OpenAI expuesta en tu frontend es el caso en el que la alarma tiene razón. Ninguna opción de configuración hace que una clave así sea segura en un navegador.
- Es un token al portador, así que basta con tenerla. No existe ninguna restricción de dominio que la ate a tu propio sitio, como sí la hay para una clave de Google.
- Rótala hoy en el panel de OpenAI, mueve la llamada detrás de un endpoint tuyo y pon un límite de gasto al proyecto.
- Encontramos una en 33 de 30.998 apps escaneadas. Rara, y las 33 salieron con nota D o F.
Abre tu app en un navegador, mira el código fuente de la página y busca dentro
sk-proj-. Si aparece una cadena larga, tu clave de API de OpenAI está expuesta
en tu frontend, y cualquier visitante que hayas tenido podría haberla copiado.
Aquí está la parte que casi todos los consejos sobre esto se equivocan: una clave de OpenAI no es una clave de Google con otro prefijo, y no hay nada que puedas configurar en un panel que la haga segura donde está. Una clave de Google Maps va en tu página, y una opción gratuita la protege. Una clave de OpenAI no tiene ninguna opción equivalente en ninguna parte, y por eso la única primera instrucción honesta es rotarla.
Entre el 12 y el 14 de agosto de 2026 pasamos nueve comprobaciones externas por 30.998 apps en producción, construidas con Lovable, Bolt, v0, Replit y Base44. Apareció una clave de OpenAI en 33 de ellas. Las 33 volvieron con nota D o F, porque un solo hallazgo crítico tapa la nota sin importar qué más hiciera bien la app. Las cifras completas están en nuestro informe de escaneo.
¿Una clave de API de OpenAI expuesta en tu frontend es un problema?
Sí. Este es el caso en el que la alarma tiene razón.
Una clave de OpenAI es un token al portador, y la palabra lleva dentro toda
la explicación: quien la porta puede usarla. Viaja en una cabecera que dice
Authorization: Bearer sk-proj-…, y los servidores de OpenAI no preguntan nada
más. Ni qué sitio web la envió. Ni de qué país vino, ni si quien la manda eres
tú.
Piensa en un billete de tren y no en un pasaporte. Un revisor no comprueba de quién es el nombre que figura en un billete, porque tenerlo es toda la acreditación. Eso es lo que hace que un billete merezca la pena robarlo y un pasaporte casi nunca.
Así que una clave impresa en tu app es un billete que le has dado a cada visitante. La mayoría no mirará nunca. Los que miran no suelen ser personas: scrapers automáticos rastrean páginas públicas recogiendo cadenas con forma de clave, y no necesitan saber quién eres para encontrar la tuya.
Por qué la variable de entorno no la escondió
Porque una compilación de frontend mete las variables de entorno dentro del archivo que publica.
Este es el paso que hace que la gente se quede tranquila. Sacaste la clave de tu
código y la pusiste en un archivo .env, la llamaste VITE_OPENAI_API_KEY o
NEXT_PUBLIC_OPENAI_API_KEY, y ahora el código nombra una variable donde antes
estaba la clave. Nada cambió en la app que se publica. La herramienta de
compilación sustituyó esa variable por su valor al salir, y el valor está en el
JavaScript que tu visitante descarga.
La documentación de Vite lo dice sin rodeos: las variables con el prefijo VITE_
quedan expuestas en el código fuente del cliente después del empaquetado, y la
información sensible como las claves de API no debe ir en una de ellas, porque
los valores se empaquetan dentro de tu código. Los prefijos VITE_ y
NEXT_PUBLIC_ no son una caja segura. Son una declaración de que entiendes que
esa variable es pública.
Una variable de entorno sí hizo una cosa real por ti: mantuvo la clave fuera de tu repositorio, donde se la habría encontrado cualquiera que leyera tu código. Tus visitantes nunca leen tu código. Leen el archivo que tu compilación produjo a partir de él.
Por qué no puedes restringirla como restringes una clave de Google
Porque OpenAI no ofrece una restricción de ese tipo.
Si has leído sobre una clave de API de Google en un frontend, te topaste con un arreglo de cinco minutos: abre la clave en la consola de Google Cloud, pon una restricción de referente HTTP, y la clave impresa en tu página funciona en tu sitio y devuelve un error en cualquier otro. Ese consejo es correcto para una clave de Google, y no se traslada.
No hay ningún campo en una clave de OpenAI para "solo desde yourapp.com". Ni lista de dominios permitidos, ni comprobación de referente, ni restricción por IP que sobreviva a un navegador. Lo que OpenAI te da en su lugar está en la cuenta que hay detrás de la clave: a qué proyecto pertenece, cuánto puede gastar ese proyecto al mes y si la clave sigue existiendo. Eso limita lo que puede costar una clave robada. La clave en sí sigue funcionando desde cualquier parte hasta que la borras.
Qué hace realmente dangerouslyAllowBrowser
Apaga una protección, y su nombre es la documentación.
La biblioteca oficial de JavaScript de OpenAI se niega a ejecutarse en un
navegador por defecto. El README dice que el soporte de navegador está
"disabled by default to avoid exposing your secret API credentials", y que
activar dangerouslyAllowBrowser "can be dangerous because it exposes your
secret API credentials in the client-side code".
Si tu app llama a OpenAI desde el navegador, esa opción está puesta a true en
algún sitio de tu código, porque la biblioteca no arranca sin ella. Alguien
escribió la palabra dangerously para quitarse de encima un mensaje de error.
Así es como suele producirse este hallazgo, y la biblioteca te lo dijo primero.
La opción tiene usos reales, y todos son estrechos: una herramienta interna donde conoces a cada usuario, una clave de desarrollo temporal, una clave tan acotada que gastarla no cuesta nada. Una app pública en internet abierto no es ninguno de esos.
Qué cuesta que alguien encuentre tu clave
Una factura, y una app que deja de funcionar.
La factura es la parte que la gente espera. Las peticiones de otra persona se cargan a tu cuenta, y las llamadas a un modelo no son baratas para lo que suele ser un proyecto paralelo. Lo habitual es que quien lo lleva se dé cuenta por una factura mayor que la del mes pasado sin ninguna razón que pueda señalar.
La caída es la parte que no espera. Tu cuenta tiene límites de tasa, y el tráfico de un desconocido se los gasta. Tu propia app empieza a devolver errores durante las horas en las que otro está ocupado, y eso se lee como un fallo y no como un robo, así que la gente pasa un día depurando lo que no es.
Hay un tercer coste, y depende de cómo estuviera acotada la clave. Una clave de API autentica todas las llamadas que puede hacer el proyecto al que pertenece, así que una clave con permisos amplios alcanza todo lo demás que haya guardado ahí: archivos subidos a la cuenta, modelos afinados, asistentes que construiste. Comprueba qué podía alcanzar la clave antes de decidir que esto iba solo de dinero.
Cómo llamar a OpenAI desde tu app sin publicar la clave
Pon algo tuyo entre tu visitante y OpenAI.
La forma es la misma en todas partes. Tu app llama a un pequeño endpoint que es tuyo. Ese endpoint guarda la clave, llama a OpenAI y devuelve la respuesta. El navegador no ve nunca la clave, porque la clave nunca sale de tu servidor.
Dónde vive ese endpoint depende de con qué hayas construido:
- Supabase en tu stack: una Edge Function, con la clave guardada como secreto en el panel de Supabase.
- Desplegado en Vercel o Netlify: una función serverless bajo
/api, con la clave en las variables de entorno de servidor del proyecto. - Lovable, Bolt o Replit: cada uno tiene su propio almacén de secretos. La regla no cambia, y la trampa tampoco: un almacén etiquetado como secretos sigue enviando el valor al navegador si el código que lo lee se ejecuta ahí.
Dos cosas que merece la pena hacer ya que estás. Pon un límite de tasa a tu endpoint, porque un endpoint que llama a OpenAI para cualquiera que pregunte es la misma factura por una vía más lenta. Y pon un límite de gasto mensual al proyecto de OpenAI, que es el único control que le pone suelo al peor caso.
Si tu app usa la API Realtime de OpenAI para voz, el navegador sí necesita una credencial de verdad, y OpenAI documenta cómo dársela: tu servidor acuña un secreto de cliente de vida corta y le entrega ese a la página. El billete sigue existiendo, caduca en minutos, y lo emitió tu propio servidor.
Cómo encontrar gratis cada clave que publica tu app
Empieza a mano, porque cuesta cinco minutos y no requiere instalar nada. Mira el
código fuente de tu sitio en producción y busca dentro sk-proj- para una clave
de OpenAI, sk-ant- para una de Anthropic, AIza para Google y eyJ para un
token de Supabase.
Lo que se escapa así es el JavaScript que la página carga después, que en una app hecha a base de vibe coding es casi todo. Nuestro escáner abre tu app en un navegador real, espera a que lleguen los bundles y lee esos en lugar del HTML. Además descodifica cada token de Supabase e informa del rol escrito dentro, así que una clave que sí va en tu página vuelve marcada como correcta y no enterrada en un muro de rojo.
La comprobación de claves es una de nueve, y las otras ocho son la razón por la que una nota te dice más que una búsqueda:
| Qué mira el escaneo | La pregunta que responde |
|---|---|
| Claves secretas en tu código | ¿Hay una clave de API de pago o de admin legible por cualquiera? |
| Reglas de la base de datos | ¿Puede un desconocido leer las filas de tus usuarios sin entrar? |
| Archivos privados | ¿Se pueden descargar archivos .env o volcados de base de datos? |
| Cabeceras de seguridad | ¿Están activadas las protecciones del lado del navegador? |
| Buckets de almacenamiento | ¿Puede cualquiera listar los archivos que suben tus usuarios? |
| Source maps | ¿Está tu código fuente original publicado junto a la app? |
| APIs abiertas y CORS | ¿Responden tus endpoints a cualquier sitio web que pregunte? |
| Caducidad del certificado | ¿Es válido el HTTPS y no está a punto de caducar? |
| Renovación del dominio | ¿Está el nombre renovado antes de que otro pueda quedárselo? |
Obtienes una nota, una puntuación y los recuentos en pantalla en unos 20 segundos, sin cuenta. Da una dirección de correo y viene con ella la lista detallada, junto con un arreglo escrito para tu builder que puedes pegar directamente.
Tres cosas que no hará, y son las razones por las que es seguro apuntarlo a una
app en producción: nunca inicia sesión, nunca escribe nada y nunca se queda con
una clave que encuentra. Un secreto expuesto se guarda como una pista enmascarada
de la forma sk-proj-…a1b2, y el valor real se descarta.
Escanea tu app, o lee antes
qué mira cada una de las nueve comprobaciones.
Qué hacer ahora mismo
Qué hacer
- Rota la clave primero, en el panel de OpenAI bajo API keys. Borrarla de tu código no cierra nada, porque el valor antiguo sigue en tu historial de versiones y en cada copia en caché de tu página.
- Pon un límite de gasto mensual al proyecto al que pertenece la clave. Es el único control que pone un techo a lo que este error o cualquier otro posterior pueden costarte.
- Mueve la llamada detrás de un endpoint tuyo, y dale a ese endpoint su propio límite de tasa. Un navegador no debería tener nunca una clave que gasta dinero.
- Lee tu página de uso para los días en que la clave estuvo activa. Rotar detiene lo que pasa a partir de ahora y no dice nada de lo que ya pasó.
- Deja el prefijo
VITE_oNEXT_PUBLIC_fuera de todo lo que te molestaría que leyera un desconocido. Esos prefijos significan público, y tu herramienta de compilación se los toma al pie de la letra.
Si prefieres recorrer esto de una sentada, la lista de seguridad de 10 minutos lo cubre junto con las otras cosas que conviene cerrar en una app recién lanzada. Y para la pregunta más amplia de qué claves van en un navegador, tenemos una guía para distinguir las publicables de las secretas y un censo de lo que 30.998 apps publicaron de verdad.
Preguntas frecuentes
Alguien encontró mi clave de OpenAI en mi app. ¿Qué hago primero?
Rotarla. Abre tu panel de OpenAI en API keys, crea una clave nueva, pon la nueva en tu servidor y borra la vieja. Borrarla de tu código no es lo mismo, porque el valor antiguo sigue en tu historial de versiones y en cualquier copia en caché de tu página. Después pon un límite de gasto al proyecto y lee tu página de uso para los días en que la clave estuvo activa.
¿Puedo restringir una clave de OpenAI a mi propio dominio, como hago con una de Google?
No. Una clave de API de Google admite una restricción de referente HTTP que la hace funcionar en tu sitio y fallar en todos los demás, y por eso una clave de Google en tu página suele ser inofensiva. OpenAI no ofrece nada equivalente. No hay lista de dominios permitidos ni comprobación de referente para una clave de API, así que los únicos controles que tienes están sobre la cuenta que hay detrás: a qué proyecto pertenece la clave, cuánto puede gastar ese proyecto y si la clave sigue existiendo.
La biblioteca de OpenAI tiene una opción dangerouslyAllowBrowser. ¿Eso la hace segura?
No, y el nombre es el aviso. OpenAI entrega el soporte de navegador desactivado, y su propio README dice que la opción es peligrosa porque expone tus credenciales secretas de API en el código del cliente. Activarla no cambia lo que el navegador puede leer; solo evita que la biblioteca se niegue a arrancar. Los casos estrechos para los que está pensada son herramientas internas con usuarios conocidos y claves de desarrollo de vida corta, no una app pública.
¿Cuánto puede gastar alguien con una clave que ha encontrado?
Todo lo que permita tu cuenta, y por eso el límite de gasto importa más que el tamaño de la factura que hayas visto hasta ahora. Una clave robada consume los mismos límites de tasa que usa tu app, así que el primer síntoma a menudo no es la factura: es tu propia app fallando mientras otra persona está ocupada. Pon un límite de gasto mensual al proyecto y tendrás un techo para todo esto.
Solo usé la clave para una demo rápida. ¿Importa igualmente?
Sí. Una clave sigue activa hasta que alguien la revoca, y no sabe que se suponía que era temporal. Los scrapers automáticos recogen cadenas con forma de clave de páginas públicas de manera continua, así que la antigüedad de la demo juega en tu contra y no a tu favor. Borrar la clave lleva menos tiempo que decidir si merecía la pena borrarla.