Qué pasó
Según el resumen de SafeState sobre el análisis de los investigadores, el ataque fue más o menos así:
- La web de Polymarket cargaba un script de monitorización de usuarios reales desde el CDN de DebugBear. Es una configuración normal. Miles de sitios hacen lo mismo con decenas de proveedores.
- El atacante sustituyó ese archivo por JavaScript malicioso. Como el navegador lo descargaba directamente del proveedor en cada visita, todos los visitantes recibieron la nueva versión al instante.
- El script comprobaba si había una sesión activa de Polymarket, leía del almacenamiento del navegador los datos de la cuenta, los detalles de la wallet y las credenciales de la API de trading, y después usaba el propio flujo de firma de Polymarket para mover posiciones o vender activos hacia una wallet del atacante.
El detalle que conviene pensar con calma: los investigadores señalaron que ni las hardware wallets ni las contraseñas fuertes sirvieron de nada, porque el script nunca necesitó una clave privada. Actuaba como el usuario con la sesión iniciada, desde dentro de su propia sesión en el navegador.
La solución de Polymarket, según el mismo informe, fue un commit en producción que eliminó el script del proveedor. El trading se reanudó esa misma tarde. SafeState también señala que no era el primer incidente con scripts de proveedores que sufría la plataforma este año.
Por qué esto importa para tu app
Una etiqueta script que apunta a cdn.some-vendor.com se ejecuta con el mismo poder que tu propio código. Puede leer el DOM, leer localStorage y sessionStorage, llamar a tu API con las credenciales del usuario y cambiar lo que muestra la página. No hay ningún sandbox entre "tu código" y "el snippet de analítica que pegaste la semana del lanzamiento".
Esto es especialmente relevante en apps lanzadas con prisa y en apps hechas con IA, porque:
- Los scripts se acumulan. Cada prompt de "añade analítica", "añade una burbuja de chat" o "añade seguimiento de errores" suele sumar otra etiqueta. Poca gente las quita después.
- Los tokens viven en el almacenamiento del navegador. Muchas plantillas de inicio guardan el token de sesión o de API en localStorage, donde cualquier script de la página puede leerlo.
- Normalmente no hay Content-Security-Policy. Sin ella, cualquier script inyectado o sustituido puede cargar más código desde cualquier sitio y enviar datos a cualquier parte.
No puedes auditar los pipelines de build de tus proveedores. Pero sí puedes limitar el daño que puede hacer un script malicioso.
Cómo revisar tu app
1. Haz una lista de todos los scripts que cargan tus páginas. Abre tu app, luego DevTools, la pestaña Network, filtra por JS y recarga. O ejecuta esto en la consola en cada página importante:
[...document.scripts].map(s => s.src || '(inline)').filter(Boolean)Apunta cada dominio que no sea tuyo. Hazlo también en las páginas con sesión iniciada, no solo en la web de marketing. Los gestores de etiquetas pueden cargar más scripts que no aparecen en el código fuente HTML.
2. Comprueba cuáles de esas etiquetas tienen atributo integrity. En la consola:
[...document.querySelectorAll('script[src]')]
.filter(s => !s.src.startsWith(location.origin))
.map(s => ({ src: s.src, sri: !!s.integrity }))3. Revisa tus cabeceras de respuesta. Ejecuta curl -sI https://yourapp.com y busca Content-Security-Policy. Si no está, o contiene script-src * o 'unsafe-inline' sin nonces, no está limitando gran cosa.
4. Comprueba dónde vive tu sesión. En DevTools, mira en Application las secciones Local Storage y Session Storage. Si ves ahí un access token, un refresh token o una API key, cualquier script de la página puede leerlo, que es justo lo que hizo el payload de Polymarket.
Cómo solucionarlo
Quita lo que no necesites. Es el paso que más valor aporta. Las recomendaciones de SafeState para los operadores empiezan por mantener un inventario de scripts de terceros y eliminar las etiquetas no esenciales de las páginas de cuenta, pago y transacciones. Tus páginas de login, facturación y ajustes rara vez necesitan una herramienta de mapas de calor.
Fija y verifica lo que conserves con Subresource Integrity (SRI). Si un proveedor ofrece archivos versionados, referencia una versión concreta y añade un hash. Si el archivo cambia, el navegador se niega a ejecutarlo:
<script
src="https://cdn.example-vendor.com/sdk/4.2.1/sdk.min.js"
integrity="sha384-REPLACE_WITH_REAL_HASH"
crossorigin="anonymous"></script>Genera tú mismo el hash a partir del archivo exacto que revisaste:
curl -s https://cdn.example-vendor.com/sdk/4.2.1/sdk.min.js \
| openssl dgst -sha384 -binary | openssl base64 -ATen en cuenta la contrapartida: SRI rompe los scripts que los proveedores actualizan in situ en una URL sin versión, algo habitual en snippets de monitorización y analítica. Para esos, o bien alojas tú una copia fijada que actualizas de forma deliberada, o bien aceptas que confías en el archivo en vivo del proveedor y lo mantienes fuera de las páginas sensibles.
Añade una Content-Security-Policy. CSP le dice al navegador qué orígenes pueden servir scripts y a qué orígenes puede enviar datos la página. Un punto de partida para una app de Next.js o Express podría ser este:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example-vendor.com;
connect-src 'self' https://api.yourapp.com;
frame-ancestors 'none';
base-uri 'self';
object-src 'none'Sé realista sobre lo que te aporta CSP aquí: si el dominio del propio proveedor está permitido, CSP no detendrá un archivo malicioso servido desde él. Lo que hace CSP es impedir que ese script cargue código desde dominios nuevos o envíe datos robados a endpoints arbitrarios que no estén en connect-src. Despliégala primero con Content-Security-Policy-Report-Only para ver qué se rompe. Nuestra guía sobre cómo añadir una Content-Security-Policy lo explica paso a paso.
Saca los tokens de sesión del alcance de JavaScript. Mueve los tokens de sesión a cookies marcadas como HttpOnly, Secure y SameSite, para que los scripts de la página no puedan leerlos. No impedirá que un script malicioso haga peticiones como el usuario mientras la página está abierta, pero evita que los tokens se roben y se reutilicen en otro sitio. Consulta los flags de cookies Secure, HttpOnly y SameSite.
Mantén limpias tus propias claves de frontend. Si un script puede leer tu bundle, puede leer cualquier secreto que hayas incluido en él, y los bots ya están buscando claves filtradas y archivos .env. Revisa nuestra guía sobre API keys expuestas en JavaScript del lado del cliente, asegúrate de no publicar source maps en producción y, si ya te has llevado un susto, nuestro artículo sobre cómo rotar los secretos de tu app sin caídas explica la limpieza.
Checklist final
Antes de cerrar DevTools, asegúrate de que:
- Tienes una lista de todos los scripts de terceros de tus páginas públicas y con sesión iniciada.
- Has quitado los scripts que ya no usas, sobre todo de las páginas de login, facturación y cuenta.
- Has fijado versiones y añadido hashes de integridad donde los proveedores lo permiten.
- Los scripts que no se pueden fijar están autoalojados o fuera de las páginas sensibles.
- Publicas una Content-Security-Policy con script-src y connect-src estrictos.
- Los tokens de sesión están en cookies HttpOnly, no en localStorage.
- Repites esta auditoría cada vez que alguien (o algún agente de IA) añade una etiqueta nueva.
Descubre en qué punto estás
Si quieres una referencia rápida, AppSafe revisa tus cabeceras de seguridad, incluido si hay una Content-Security-Policy configurada, y su escaneo completo verificado analiza problemas de frontend como Subresource Integrity y librerías de JavaScript vulnerables. Puedes ver cómo es un informe en el informe de ejemplo, o combinar esto con nuestra checklist previa al lanzamiento para apps hechas con IA.
Polymarket encontró la causa en aproximadamente una hora tras detener el trading. Muchos equipos pequeños tardarían mucho más en notar que un script ha cambiado. Ejecuta un escaneo gratuito de AppSafe en tu app hoy y descubre en qué punto estás.
