La página web que se llamaba localhost
El 17 de agosto, CISA añadió la CVE-2025-62593 de Ray a su catálogo de Vulnerabilidades Explotadas Conocidas. Es ejecución remota de código en la propia máquina del desarrollador, entregada por una página web que visitó, alcanzando un servicio que solo escuchaba en 127.0.0.1. Los agentes de código llevan todo el año llenando esa interfaz de servidores sin autenticación. Bromure Agentic Coding los pone donde ninguna pestaña del navegador puede marcar.
Tu servidor de desarrollo, tu notebook y tu panel local enlazan cada uno un
puerto sin autenticación a 127.0.0.1, con el razonamiento de que solo quien
está al teclado puede alcanzarlo. Una página web también puede. La política
del navegador que se interpone se deshace en unos sesenta segundos.
El 17 de agosto, CISA añadió una vulnerabilidad a su catálogo de Vulnerabilidades Explotadas Conocidas y dio a las agencias federales hasta el 20 de agosto para resolverla. The Hacker News lo contó a la mañana siguiente. La entrada es la CVE-2025-62593, un fallo de inyección de código en Ray, el framework de cómputo distribuido que buena parte de la industria usa para entrenar y servir modelos.
El código se ejecuta en el portátil del propio desarrollador. Alguien tenía Ray corriendo en local para su trabajo, abrió una página web, y la página ejecutó un comando por él.
Tres hechos corrientes, mal colocados
El aviso lo puntúa con 9,4 y describe una cadena sin ningún paso exótico dentro.
La API de Jobs de Ray acepta un comando de shell. Eso es el producto: haces
POST a /api/jobs/ con un entrypoint y el clúster ejecuta el comando. El
panel escucha en el puerto 8265, y ni él ni los endpoints de jobs exigen
autenticación. Fue una decisión deliberada de los mantenedores, cuya postura es
que
la seguridad y el aislamiento deben imponerse fuera del clúster de Ray.
Lo pones en algún sitio donde solo lleguen llamantes de confianza, y la interfaz
de loopback de un portátil suena a un sitio así.
Ray sí llevaba una guarda contra los navegadores. Comprobaba si la cabecera
User-Agent empezaba por Mozilla y rechazaba esas peticiones, dando por
supuesto que un navegador no puede mentir sobre su propio User-Agent. En
Firefox y Safari, la API Fetch permite a una página fijar esa cabecera con el
valor que le apetezca.
Queda la política del mismo origen, el único control que se interpone entre una
página de internet y un servicio de tu máquina. El DNS rebinding la desmonta. El
atacante sirve la página desde un dominio cuyos registros DNS controla, con un
tiempo de vida muy corto. La página carga y luego vuelve a resolver ese mismo
dominio a 127.0.0.1. El navegador la sigue tratando como el mismo origen,
mismo hostname y mismo puerto, mientras cada petición va ya a tu máquina. La
prueba de concepto publicada usa
singularity, un framework de
rebinding que lleva años siendo público.
Encadena todo eso y una página maliciosa, o un anuncio envenenado en una página
en la que confiabas, publica un job en tu instancia de Ray con un comando de
shell dentro. Chrome está protegido por un bug sin relación; Firefox y Safari no
lo están. Avi Lumelsky, de Oligo, teorizó el bypass del User-Agent; Jonathan
Leitschuh, entonces en Socket, construyó la cadena de rebinding y la
divulgación. Ray 2.52.0 lo corrige y añade autenticación por token opcional,
desactivada por defecto.
Los atacantes llegaron antes que el aviso. La botnet RondoDox recogió el fallo dos días antes de la divulgación pública, y la investigación ShadowRay 2.0 de Oligo sigue una campaña que convierte los despliegues de Ray en una botnet autopropagante: los clústeres comprometidos escanean en busca de más instancias de Ray y las infectan, un cron job descarga cargas nuevas desde repositorios controlados por el atacante cada quince minutos, y la carga mina Monero, abre shells inversas y se lleva lo que encuentra. Oligo contó más de 230.000 servidores Ray expuestos, diez veces más que en su primer informe de 2024. Un solo clúster comprometido entregó 240 GB de código fuente, modelos y conjuntos de datos.
Qué está escuchando en tu máquina
Deja Ray a un lado y cuenta otra cosa: los servidores HTTP sin autenticación enlazados a tu interfaz de loopback ahora mismo, y cuántos de ellos sabrías nombrar sin mirar.
Ejecuta lsof -iTCP -sTCP:LISTEN -P después de una semana de trabajo y la lista
sale más larga de lo que supondrías. Vite en el 5173. Un servicio FastAPI bajo
uvicorn --reload en el 8000. Jupyter en el 8888, mlflow ui en el 5000, el
puerto publicado de un contenedor de Postgres, un servidor MCP en el 3000, un
puerto de depuración que dejó abierto una tanda de tests que se cayó. Ninguno
pregunta quién eres. Sus autores hicieron la suposición que hicieron los
mantenedores de Ray, y aguanta hasta que una pestaña de otra ventana se pone a
hacer peticiones.
Los agentes cambiaron el volumen. Un agente que trabaja un ticket arranca servicios como cosa natural. Levanta el servidor de desarrollo para comprobar su propio cambio, sube una base de datos para pasar la migración, arranca el notebook para mirar los datos, lanza la API para golpear un endpoint. Lo hace varias veces por tarde, en varios espacios de trabajo, y no recoge nada, porque nada se lo dice. Los procesos sobreviven a la tarea. Para el jueves la interfaz lleva una docena de servicios que no arrancaste tú y que no podrías enumerar de memoria, cada uno enlazando sin autenticación porque la guía rápida del framework dice que en localhost no pasa nada.
La postura de Ray se lee distinta en una máquina que ejecuta un agente de código: la seguridad y el aislamiento deben imponerse fuera de la cosa que arrancaste. En un portátil donde el agente y el navegador comparten un solo kernel, “fuera” no tiene dónde sentarse.
En un espacio de trabajo Bromure, el 8265 no está en tu Mac
Bromure Agentic Coding ejecuta cada espacio de trabajo en su propia VM Ubuntu sobre el framework Virtualization de Apple. Esa es la respuesta entera a este ataque, y aguanta sin que nadie parchee nada.
Cuando el agente ejecuta ray start --head, el panel se enlaza a
127.0.0.1:8265 dentro de la VM. La interfaz de loopback de tu Mac es otra
interfaz. Un fetch() con rebinding desde Safari en el anfitrión resuelve
evil.example a 127.0.0.1, conecta con el loopback de macOS y no obtiene
nada, porque el servicio que la página venía a buscar está un kernel más allá.
La cadena publicada apunta a 127.0.0.1, la dirección a la que singularity
hace rebinding y la dirección que nombra el aviso, y en un espacio de trabajo
Bromure esa dirección no tiene nada.
Lo mismo vale para la segunda mitad del aviso, la parte sobre usar un navegador como intermediario para alcanzar instancias de Ray dentro de una red corporativa que nadie puso en internet. Los espacios de trabajo funcionan en modo NAT por defecto, y el manual es específico sobre lo que eso compra: las VM son alcanzables desde tu Mac pero no quedan expuestas en tu LAN física, y las conexiones entrantes desde otro sitio no son posibles salvo que publiques un servicio a propósito. Publicar es un túnel rápido de Cloudflare por servicio que arrancas pulsando un botón.
Y te llevas el inventario. La tarjeta Puertos a la escucha del panel del
espacio de trabajo consulta ss -tulnpH dentro del invitado y lista cada socket
alcanzable desde fuera como el IP-de-la-VM:puerto al que te conectarías, con
el nombre del proceso que lo tiene. La tarjeta oculta los sockets de solo
loopback, ya que nada fuera de la VM puede alcanzarlos. La misma lista sale de
bromure-cli vm <id> -L. Esa lista es el censo de los restos del agente, y
macOS no lleva ninguna lista así por ti.
El salto siguiente al primero
ShadowRay 2.0 merece leerse por lo que hace después un nodo comprometido, porque esa es la parte de la que el aislamiento tiene que responder. Escanea en busca de otras instancias de Ray y las infecta. Instala un cron job que recoge instrucciones nuevas cada quince minutos. Va a por las credenciales y los conjuntos de datos que haya en la máquina.
Cada una de esas cosas es una conexión saliente, y un espacio de trabajo Bromure contrasta las conexiones salientes con reglas que has escrito tú. El editor Conexiones salientes del panel Guardrails guarda un conjunto de reglas al estilo pf: permitir o denegar, por host, IP o CIDR, protocolo y puerto, evaluadas de arriba abajo con la primera coincidencia ganando, más un control para el tráfico que no coincide con nada.
allow web api.github.com:443
allow tcp registry.npmjs.org:443
deny any 10.0.0.0/8
deny any 192.168.0.0/16
default deny
Dos capas aplican ese mismo conjunto de reglas. El conmutador virtual evalúa
cada flujo por IP de destino y por los hostnames que aprendió espiando las
propias respuestas DNS del invitado, de modo que una regla escrita contra un
nombre sigue coincidiendo cuando la dirección se mueve por debajo. El proxy MITM
evalúa las mismas reglas por SNI de TLS, y para las reglas web por método
HTTP, así que puedes escribir allow web api.example.com GET,HEAD. Bromure
inyecta un reset en una conexión TCP denegada, de manera que el connect falla en
lugar de quedarse colgado.
Toda esa aplicación se ejecuta en tu Mac, fuera de la VM. La interceptación
transparente viene activada por defecto y desvía el HTTP y el HTTPS del invitado
hacia Bromure incluso si algo de dentro borra HTTP_PROXY y HTTPS_PROXY. Un
agente que leyó la página web equivocada, o una carga que entró con un paquete,
queda del lado equivocado de las reglas que necesitaría editar. Bromure escribe
cada decisión en Ventana → Línea de tiempo de seguridad como una fila de
Cortafuegos: host, puerto, permitido o bloqueado, en una única tabla cronológica
junto a las decisiones de credenciales, cadena de suministro e inyección de
prompt.
El cron de quince minutos tiene el mismo problema en la otra dirección. Un
espacio de trabajo son tres capas de almacenamiento, y dos de ellas son tuyas
para tirarlas: Borrar home… devuelve /home/ubuntu a su estado posterior al
clonado, Restablecer a la base… vuelve a clonar el disco de sistema del
espacio de trabajo desde la imagen base, y la imagen base sigue siendo de solo
lectura en ejecución. Una crontab en el directorio home no sobrevive a ninguna
de las dos.
localhost era una convención
Los mantenedores de Ray no fueron descuidados. Escribieron un framework de cómputo que ejecuta comandos, lo dijeron en la documentación, y te dijeron que pusieras una frontera alrededor. La gente que lo ejecuta en un portátil tomó la interfaz de loopback por esa frontera, que es la conclusión a la que llevan invitando servidores de desarrollo, notebooks y paneles locales desde hace veinte años. Aquello era una convención, no un control. Aguantó mientras las únicas cosas en tu máquina eran cosas que arrancabas tú, y dejó de aguantar en cuanto los navegadores tuvieron un motor de scripts.
Un agente ensancha la brecha cada tarde, un npm run dev cada vez. El cuidado
no la cierra, porque el cuidado exige saber qué está escuchando, y la lista
cambia cada vez que el agente ejecuta un comando.
Deja de permitir que los dos compartan una interfaz. Pon el trabajo del agente
en su propia máquina, donde los puertos que abre responden a tu Mac y a nadie
más, donde los sockets alcanzables son una lista que lees en vez de una
suposición que heredaste, y donde las conexiones que salen se contrastan con
reglas que el espacio de trabajo no puede editar. Una página que hace rebinding
a 127.0.0.1 se encuentra entonces una interfaz vacía y se lleva una negativa.
Fuentes: CISA, “CISA Adds One Known Exploited Vulnerability to Catalog” (17 de agosto de 2026) · The Hacker News, “CISA Flags Actively Exploited Ray Flaw That Can Trigger Browser-Based RCE” (18 de agosto de 2026) · Aviso de GitHub GHSA-q279-jhrf-cc6v (CVE-2025-62593) · Oligo Security, “ShadowRay 2.0”