El marketplace llevaba funcionando ocho meses. Dos cofundadores, ninguno con experiencia en seguridad, lo habían construido en Bubble.io con Stripe Connect para los pagos. Había procesado más de $40,000 en transacciones, tenía 1,500 usuarios registrados y crecía constantemente gracias al boca a boca.
A los doce minutos del escaneo de Penetrify, se encontró un hallazgo Crítico: la clave secreta de la API de Stripe estaba incrustada en un archivo JavaScript del lado del cliente que se servía al navegador de cada visitante. Había estado allí desde el lanzamiento. Cuatro meses de acceso completo de lectura/escritura a toda su cuenta de Stripe, disponible para cualquiera que abriera las DevTools.
Esta es la historia de cómo un negocio de $40,000 estuvo a punto de convertirse en una advertencia.
La clave API de Stripe que nunca deberías exponer
Stripe emite dos tipos de claves API: claves publicables y claves secretas.
La clave publicable (pk_live_...) está diseñada para ser utilizada en código frontend. Solo puede realizar operaciones limitadas —crear tokens de métodos de pago, confirmar pagos— y no puede acceder a datos sensibles de la cuenta. Es seguro exponerla en JavaScript del lado del cliente.
La clave secreta (sk_live_...) es un asunto completamente diferente. Con una clave secreta de Stripe, puedes:
- Listar todos los clientes, métodos de pago y suscripciones
- Leer huellas dactilares completas de tarjetas y direcciones de facturación de todos los clientes
- Emitir reembolsos arbitrarios en cualquier transacción
- Crear cargos contra cualquier método de pago guardado
- Modificar o eliminar programas de pago y detalles de cuentas bancarias
- Acceder a todas las Connected Accounts (en este caso, todas las cuentas de pago de vendedores en el marketplace)
- Recuperar los detalles de la cuenta bancaria de cada vendedor que se había incorporado a través de Stripe Connect
La clave secreta de Stripe es la clave maestra de toda tu infraestructura de pagos. Solo debe estar en código del lado del servidor, en variables de entorno que nunca se envían al navegador.
Cómo terminó en el paquete
Bubble.io es una plataforma no-code que te permite construir aplicaciones web full-stack sin escribir código. Tiene un sistema de flujos de trabajo integrado para la lógica del lado del servidor y un conector API para servicios externos. Para un equipo de dos personas sin experiencia en ingeniería, es una forma legítima de lanzar un producto real rápidamente.
La clave secreta de Stripe terminó en el frontend por una razón que resultará familiar a cualquiera que haya construido en una plataforma no-code bajo presión de tiempo: funcionó.
Durante el desarrollo, el equipo necesitaba realizar llamadas a la API de Stripe desde los flujos de trabajo de Bubble. Añadieron la clave secreta a la configuración del conector API. En algún momento de la configuración —ya fuera por una mala interpretación de la ejecución del lado del cliente frente a la del lado del servidor, o por un detalle de configuración de Bubble que no era obvio— la clave terminó incluyéndose en la carga útil de JavaScript enviada al navegador. Los flujos de pago funcionaron. Las transacciones se procesaron correctamente. No hubo ningún error, ninguna advertencia, ningún síntoma obvio.
La plataforma de Bubble maneja gran parte de la ejecución del lado del servidor correctamente por defecto, pero el límite entre lo que se ejecuta en el navegador y lo que se ejecuta en los servidores de Bubble no siempre es visualmente obvio en el constructor no-code. Este es un riesgo bien documentado en la comunidad de desarrolladores de Bubble, pero es fácil pasarlo por alto cuando te concentras en hacer que el producto funcione en lugar de auditar qué datos envía tu aplicación al cliente.
Lo que realmente significó la exposición
Seamos específicos sobre lo que estuvo en riesgo durante esos cuatro meses.
Cualquiera que visitara el marketplace podía abrir las Chrome DevTools, navegar a la pestaña Sources o Network, buscar en los archivos JavaScript sk_live_ y encontrar la clave. Esto no requiere herramientas de hacking, conocimientos especiales ni ninguna vulnerabilidad más allá de la capacidad de hacer clic derecho e inspeccionar una página web.
Con esa clave, podrían haber:
- Vaciado el saldo de Stripe del negocio emitiendo reembolsos en transacciones completadas, revirtiendo ingresos que ya se habían ganado
- Enumerado todos los registros de clientes incluyendo nombres, direcciones de correo electrónico, detalles parciales de tarjetas y direcciones de facturación — una violación de datos reportable bajo GDPR y varias leyes de privacidad estatales de EE. UU.
- Accedido a los detalles de la cuenta bancaria de los vendedores para cada vendedor que había conectado su cuenta de pago a través de Stripe Connect — nombres, números de cuenta, números de ruta
- Modificado los programas de pago para redirigir los pagos a vendedores a cuentas controladas por atacantes
- Creado cargos fraudulentos contra cualquier método de pago de cliente guardado, hasta los límites de Stripe para la cuenta
Cualquiera de estos resultados habría acabado con el negocio. Combinados, representan el tipo de brecha catastrófica que llega a las charlas de conferencias de seguridad.
La clave de Stripe había estado expuesta durante 4 meses. Durante ese tiempo, aproximadamente 8,000 personas habían visitado el marketplace. Cualquiera de ellas podría haberla encontrado.
Otros Hallazgos
La clave de Stripe fue el hallazgo más grave, pero el escaneo reveló cuatro problemas adicionales:
MEDIO — Reglas de privacidad de Bubble mal configuradas
Las reglas de privacidad de Bubble controlan qué campos de la base de datos son visibles para diferentes roles de usuario. Los registros de perfil del vendedor —que incluían detalles de cuentas bancarias ingresados durante la incorporación a Stripe Connect— eran visibles para cualquier usuario autenticado a través de la API de datos de Bubble. Incluso sin la clave de Stripe, cualquier comprador con sesión iniciada podría haber consultado información financiera del vendedor.
MEDIO — Enumeración de cuentas a través de restablecimiento de contraseña
El flujo de restablecimiento de contraseña devolvía respuestas diferentes para direcciones de correo electrónico registradas frente a no registradas. Una solicitud para un correo electrónico registrado devolvía "Check your inbox"; una solicitud para un correo electrónico no registrado devolvía "No account found." Esto permite a un atacante construir una lista de qué direcciones de correo electrónico tienen cuentas en la plataforma — útil para phishing dirigido o credential stuffing.
MEDIO — Sin Content Security Policy
La aplicación no devolvía ningún encabezado Content-Security-Policy. La funcionalidad de búsqueda reflejaba la entrada proporcionada por el usuario sin codificación en algunos contextos, lo que hacía posible el XSS reflejado. Sin una CSP, una carga útil de XSS podría exfiltrar tokens de sesión, realizar llamadas a la API autenticadas en nombre de la víctima o inyectar scripts maliciosos en la página para otros visitantes.
BAJO — Comodín CORS
La API devolvía Access-Control-Allow-Origin: *, permitiendo que cualquier sitio web realizara solicitudes de origen cruzado a los puntos finales de la API y leyera las respuestas. Para los puntos finales que devuelven datos sensibles, esto permite la exfiltración de datos de sitio cruzado desde una página maliciosa que la víctima visita.
La Respuesta: Inmediata y Efectiva
Los fundadores actuaron en la hora siguiente a la recepción del informe.
El primer paso fue rotar la clave de Stripe comprometida. En el panel de control de Stripe, bajo Desarrolladores → Claves de API, la clave secreta en vivo puede ser rotada — generando una nueva clave e invalidando inmediatamente la antigua. Esto tomó aproximadamente dos minutos. Desde ese momento, cualquiera que hubiera extraído la clave expuesta ya no podía usarla.
El segundo paso fue auditar los registros de eventos de Stripe en busca de actividad no autorizada. El Panel de Control de Stripe proporciona un registro completo de cada llamada a la API realizada con sus claves, incluyendo la dirección IP y la marca de tiempo de cada solicitud. Los fundadores revisaron el registro de eventos de los cuatro meses anteriores buscando llamadas anómalas — reembolsos que no emitieron, clientes que no crearon, cambios de pago que no realizaron. No encontraron ninguno. La clave había estado expuesta pero — hasta donde se pudo determinar — no había sido abusada activamente.
El tercer paso fue corregir la configuración de Bubble. Trabajando con un desarrollador de Bubble que contrataron por unas horas, movieron todas las llamadas a la API de Stripe a los flujos de trabajo de backend del lado del servidor de Bubble, donde las claves de API no se transmiten al navegador. Las reglas de privacidad de Bubble también se corrigieron para restringir los datos financieros del vendedor únicamente a la cuenta de vendedor relevante.
Cómo encontrar esto en tu propia aplicación
Si estás ejecutando una aplicación Bubble, Webflow o cualquier otra aplicación sin código que se integre con Stripe, así es como puedes verificar si tienes este problema:
- Abre tu aplicación en una ventana de incógnito.
- Abre las Herramientas para desarrolladores de Chrome (F12) → pestaña Red.
- Recarga la página.
- En la pestaña Red, busca archivos JavaScript. Para cada uno, haz clic y busca (Ctrl+F)
sk_live_. - También revisa la pestaña Fuentes. Usa Ctrl+Shift+F para buscar
sk_live_en todos los scripts cargados.
Si encuentras tu clave secreta de Stripe en cualquiera de estos archivos, rótala inmediatamente antes de hacer cualquier otra cosa, y luego investiga cómo llegó allí.
Esta misma verificación se aplica a cualquier otra clave de API sensible: OpenAI, Twilio, SendGrid, AWS, Mailchimp. Cualquier clave con acceso de escritura o acceso a datos sensibles que encuentres en el JavaScript del lado del cliente debe ser tratada como comprometida y rotada inmediatamente.
Por qué persiste este patrón
La exposición de claves secretas en los paquetes de frontend no es una nueva clase de vulnerabilidad. Ha sido un riesgo conocido y bien documentado desde que las aplicaciones web utilizan API de terceros. Entonces, ¿por qué sigue ocurriendo?
La respuesta es que la experiencia de desarrollo lo facilita. Las plataformas sin código y los modernos frameworks de JavaScript difuminan la frontera entre el cliente y el servidor de maneras que no estaban presentes en modelos anteriores de desarrollo web. Las variables de entorno precedidas por NEXT_PUBLIC_ se envían intencionalmente al navegador; las que no tienen el prefijo no lo hacen. El contexto de ejecución de Bubble depende del tipo de flujo de trabajo que estés utilizando. Las configuraciones de paquetes de Vite y webpack determinan qué termina en el navegador.
Estas fronteras están documentadas pero no se aplican a nivel de herramientas. No hay un error en tiempo de compilación cuando expones accidentalmente una clave secreta. La aplicación funciona correctamente. La exposición es silenciosa, indefinida y crece cada día que la clave sigue siendo válida.
La única defensa confiable es escanearla explícitamente, y rotarla rápidamente cuando la encuentres.
