Volver a todas las publicaciones
Publicado el · por Renaud Deraison

La wordlist ya conocía tu directorio personal

F5 Labs registró 807 ataques y 32.000 eventos en un solo mes contra servidores de desarrollo Vite expuestos, mediante un truco de cadena de consulta que pasa de largo ante la propia deny-list de Vite. Los escáneres no buscan tu aplicación. Piden /home/ubuntu/.aws/credentials por ruta absoluta, y /proc/self/environ, y terraform.tfstate. En un workspace de Bromure Agentic Coding ese puerto mira a un switch privado, y el archivo que la wordlist quiere no guarda ninguna clave.

Este atacante no te envía ningún paquete ni ningún archivo comprimido. Es tu propio servidor de desarrollo el que está escuchando, y un desconocido le pide archivos por su nombre. Qué máquina responde decide lo que se lleva.

Le pides al agente que levante el frontend para poder mirarlo. Teclea npm run dev, abre el puerto, carga la página, y tú vuelves a revisar el diff. En algún punto de ese bucle, en un docker-compose.yml o en una bandera --host que añadiste hace semanas para que la vista previa fuera accesible desde el móvil, el servidor se ató a todas las interfaces en vez de al loopback. Doce minutos después, algo que se presenta como Googlebot le pide a tu servidor de desarrollo /@fs/../.env?raw?? y recibe un HTTP 200.

F5 Labs publicó las cifras el 11 de septiembre. A lo largo de un solo mes de datos de sensores, agosto de 2026, sus honeypots registraron 807 ataques agrupados por sesión y unos 32.000 eventos en bruto dirigidos a servidores de desarrollo Vite expuestos, frente a una línea base de 1.732 eventos en los tres meses anteriores. Eso es dieciocho veces el tráfico, de un mes a otro, por un solo fallo. BleepingComputer lo cubrió el 14 de septiembre.

Una cadena de consulta que esquiva la deny-list

Vite sirve archivos del sistema de archivos del host mediante una ruta interna llamada /@fs/, que es como el servidor de desarrollo le entrega a tu editor un módulo que vive fuera de la raíz del proyecto. Como esa ruta puede llegar a cualquier parte, Vite incluye una deny-list, server.fs.deny, para bloquear los objetivos obvios: archivos .env, certificados, fuentes privadas.

CVE-2026-39364, publicada el 7 de abril, permite a un atacante saltarse esa deny-list adornando la petición con una cadena de consulta. F5 describe el mecanismo:

El servidor procesa la petición, normaliza la ruta y elimina o malinterpreta la cadena de consulta durante la validación de acceso, sin llegar a disparar la comprobación server.fs.deny.

El servidor no aplica el filtrado por deny-list y sirve el archivo objetivo con una respuesta HTTP 200.

El fallo afecta a Vite 7.1.0 hasta 7.3.2, y a las 8.x anteriores a 8.0.5. F5 capturó estas formas de petición en la práctica, cada una lo bastante corta para leerse en una línea: GET /@fs/.env?raw??, GET /@fs/../.env?raw??, GET /@fs/..%252f..%252f..%252f..%252froot/.env?raw?? y GET /@fs/..%252f..%252f..%252f..%252fproc/self/environ?raw??. Otras variantes del mismo utillaje usan ?import&raw, ?import&url&inline, ?inline&import y ?raw?import.

Esos trucos de parámetros llevan sus propios números de CVE: CVE-2025-30208, CVE-2025-31125 y CVE-2024-45811, tres elusiones más antiguas de @fs todavía cargadas en los mismos escáneres. Quien opera la flota de escaneo no se reequipó para un fallo nuevo. Añadió una línea más a un archivo que ya tenía.

una petición, un archivoescánerGET /@fs/../.env?raw??User-Agent: Googlebot/2.1servidor de dev viteruta normalizada para servirla/@fs/ → lectura de discoserver.fs.denyconsulta mal tratada, comprobación omitidaarchivo leído y devueltosin auth, sin sesión, sin línea de log que leasHTTP 200el contenido del archivo,en el cuerpo de la respuestaya en los mismos escáneresCVE-2025-30208 · ?raw??CVE-2025-31125 · ?inline&importCVE-2024-45811 · ?import&raw
La elusión en un solo intercambio. La ruta /@fs/ de Vite existe para servir archivos del proyecto, y server.fs.deny existe para mantenerla lejos de los secretos. Añadir una cadena de consulta al final, cuya forma habitual es ?raw??, hace que la ruta se normalice para servirla pero se trate mal durante la comprobación de acceso, así que la deny-list se queda callada y el archivo vuelve con un 200. El path traversal de la misma petición sale del proyecto, y así es como un servidor de desarrollo de frontend acaba leyendo /proc/self/environ.

La wordlist es un mapa de la máquina de un desarrollador

Lee lo que piden los escáneres y fíjate en qué poco de ello tiene que ver con tu aplicación.

Piden .env, .env.local, .env.production, .env.development y .env.staging. Piden terraform.tfstate, terraform.tfvars, .terraform/terraform.tfstate, serverless.yml y .serverless/serverless-state.json. Piden .azure/credentials y .azure/accessTokens.json. Piden /etc/passwd, /proc/1/environ, /proc/self/cwd/.env y /proc/self/environ. Esa última ruta guarda el bloque de entorno del propio proceso del servidor de desarrollo, donde acaba una API_KEY que exportó tu shell.

Y luego piden credenciales de AWS recorriendo la lista de directorios personales bajo los que podría estar ejecutándose el proceso de un desarrollador:

/root/.aws/credentials, /home/ec2-user/.aws/credentials, /home/ubuntu/.aws/credentials, /home/node/.aws/credentials, /home/www-data/.aws/credentials, /home/admin/.aws/credentials, /home/debian/.aws/credentials, /var/www/.aws/credentials, /usr/src/app/.aws/credentials, /app/.aws/credentials. Luego .aws/config, .aws/credentials.backup, .aws/credentials.bak, .aws/sso/cache/, rootkey.csv, aws-exports.js y amplifyconfiguration.json.

Esas rutas inventarían la máquina de un desarrollador, enumerada por nombre de usuario, y Vite es solo la puerta. El fallo es incidental; tres fallos más antiguos de la misma ruta viajan en las mismas peticiones. Los operadores apuestan a que un proceso que escucha en una dirección enrutable corre bajo un usuario en cuyo directorio personal hay claves de verdad.

El tráfico va vestido para sobrevivir a una mirada distraída a los registros. F5 anotó cabeceras User-Agent falsificadas que rotan entre Googlebot/2.1, ClaudeBot/1.0, GPTBot/1.4, PerplexityBot/1.0, OAI-SearchBot/1.3 y Amazonbot/0.1, además de valores falsos de X-Forwarded-For y X-Real-IP para sortear las allow-lists de IP. Las fuentes están en los rangos 34.x y 35.x de Google Cloud, repartidas por varias regiones, encabezadas por Estados Unidos con 17.297 eventos, luego Bélgica con 4.407 y los Países Bajos con 4.011. Este mes, una línea de tu registro de acceso que dice ser un crawler de IA es una prueba endeble de que la haya enviado un crawler.

Dos de las recomendaciones de F5 son preguntas de arquitectura

F5 cierra con cinco consejos. Tres son corrientes y correctos: actualizar a 7.3.2 u 8.0.5, filtrar /@fs/ en el borde, verificar los crawlers por DNS inverso en lugar de fiarse de la cabecera. Los otros dos describen una postura que hay que sostener más que una tarea que se termina.

Asegúrate de que los servidores de desarrollo no se aten a interfaces externas. Audita las configuraciones de Docker compose, las reglas de ingress de Kubernetes y los grupos de seguridad en la nube.

Rota los secretos expuestos: si un servidor de desarrollo Vite sin parchear estuvo accesible desde redes externas durante agosto de 2026, trata las variables .env locales, las credenciales de AWS, los tokens de acceso de Azure y los archivos de estado de Terraform como potencialmente comprometidos.

La primera te pide mantener una promesa sobre cada puerto, en cada archivo compose, a lo largo de cada rama, mientras viva el proyecto, en un momento en que quien teclea npm run dev suele ser un agente y no tú. La segunda pregunta qué haces después, y responde: rota lo que la máquina pudo ver.

Las dos se vuelven más fáciles si el puerto se abre en otro sitio, y si los archivos que la wordlist nombra no guardan nada que merezca rotarse.

Dónde coloca el puerto un workspace de Bromure

Bromure Agentic Coding ejecuta agentes de código dentro de una VM Linux virtualizada por hardware en tu Mac, con los controles de seguridad en el lado del host de esa frontera. Dos de ellos responden a esta campaña.

El servidor de desarrollo se ata dentro de un switch privado. En el modo NAT por defecto, cada VM de workspace se engancha a un único switch L2 por software a escala de proceso, multiplexado sobre una sola interfaz vmnet, en una subred privada. Esa subred es 192.168.64.0/24 salvo que tu propia LAN ya la use, Bromure ejecuta en ella su propio servidor DHCP, y cada workspace conserva una MAC determinista y una concesión estable. El manual enuncia la consecuencia: tu Mac puede llegar a las VM, tu LAN física no las ve, y las conexiones entrantes desde cualquier otro sitio son imposibles a menos que publiques tú mismo un servicio. Un agente que se ata a 0.0.0.0 dentro de esa VM se ha atado a todas las interfaces que posee, y cada una de ellas mira a un switch que empieza y acaba en tu portátil. No tienes ningún grupo de seguridad que auditar, porque no tienes ninguna ruta de entrada que asegurar.

Aun así puedes ver qué está escuchando, y es una lista, no una auditoría. El panel del workspace lleva una tarjeta Listening Ports que muestra cada socket alcanzable desde fuera en el invitado, como un endpoint <IP-VM>:<puerto> con copia en un clic. Eso convierte la recomendación n.º 2 de F5 en una tarjeta que miras de reojo, refrescada desde el invitado cada segundo y medio, en vez de en una auditoría que programas. Cuando sí quieres que el mundo vea una vista previa, un servicio probablemente HTTP recibe un botón de globo que publica ese servicio concreto mediante un quick tunnel de Cloudflare, tras un diálogo de consentimiento de una sola vez. Expones un servicio haciendo clic en él, servicio a servicio, en el host, en lugar de dejando viva una bandera en un archivo compose.

Conservas el motivo por el que recurriste a --host en primer lugar. El sidecar Chromium desechable comparte el mismo segmento L2 que la VM del workspace, así que el navegador integrado carga el servidor de desarrollo del agente en la dirección de la VM. No en localhost, puesto que el navegador corre en una máquina aparte.

Si otras máquinas sí necesitan llegar a la VM, el modo Bridged la une a tu LAN física. Eso lo fijas por workspace en el editor de workspace, en el host, y cae de vuelta a NAT si la interfaz no está disponible al arrancar. Esa elección la haces en un panel, no en un archivo de configuración que el agente pueda editar.

una máquina de dev expuestauna dirección enrutable, un usuario real, archivos realesGET /@fs/../.env?raw??llega desde 34.x, 200 OK/home/ubuntu/.aws/credentialsun id de clave de acceso y un secreto vivo/proc/self/environcada token que exportó el shelldespuésrotar las claves, los tokens, el archivo de estado,y adivinar la ventanaen un workspaceun switch privado, un home lleno de sustitutosninguna ruta de entrada por la que llegarvmnet NAT · 192.168.64.0/24tarjeta Listening Portscada socket abierto, y un globo para publicar unola wordlist aterriza en falsificacionessk-ant-api03-brm-… · ghp_… · credential_processdespuésreiniciar el disco si quieres,y no rotar nada
El mismo escaneo, contra dos máquinas. En una máquina de desarrollo con dirección enrutable, la petición llega a un proceso que corre como un usuario real, y cada ruta de la wordlist resuelve a un archivo real: .env con claves vivas, ~/.aws/credentials con un secreto, /proc/self/environ con los tokens exportados por el shell. En un workspace de Bromure Agentic Coding el puerto mira a un switch vmnet privado sin ruta de entrada. Publica el servicio a propósito y la wordlist aterriza igualmente sobre sustitutos, y sobre un ~/.aws/config que nombra a un ayudante en vez de guardar una clave.

Y si publicas el servicio a propósito

A veces sí quieres la vista previa en internet, para un cliente o un compañero. Así que pulsa el globo, abre el túnel y deja que los escáneres la encuentren. Baja por la wordlist, entrada a entrada, y mira qué vuelve.

.env y /proc/self/environ devuelven el entorno del workspace, y cada credencial que hay en él es falsa. Bromure deriva cada sustituto del valor real más una sal de 32 bytes propia de la instalación, mediante HKDF-SHA256, conservando la forma que espera un validador del lado del cliente: una clave de Anthropic se lee sk-ant-api03-brm-…, un token de GitHub es ghp_ más 36 caracteres, el de GitLab es glpat- más 20. Bromure escribe esos sustitutos en las variables de entorno y en ~/.git-credentials, ~/.docker/config.json, ~/.kube/config y las configuraciones MCP. Los valores reales nunca entran en la VM; se quedan cifrados en el Mac, y un proxy del lado del host cambia cada uno en el cable en el último momento, acotado al host de destino al que pertenece.

/home/ubuntu/.aws/credentials es la entrada más afilada, porque /home/ubuntu es el directorio personal de una VM de workspace de Bromure. El escáner acierta con qué usuario ejecuta el servidor. Aun así el archivo no está. La configuración de AWS de Bromure no escribe ningún archivo de credenciales; escribe ~/.aws/config con una línea credential_process que apunta a un ayudante que entrega, por un socket del host, tu ID de clave de acceso real emparejado con una clave secreta falsa de cuarenta caracteres, y omite el token de sesión. Los SDK de AWS, la CLI aws, terraform y boto3 lo toman todos por su cuenta. Un escáner que lee el archivo de configuración obtiene la ruta de un ayudante al que no puede llamar.

Llévate la clave secreta falsa de todos modos y te habrás llevado algo inerte. El invitado firma sus peticiones con esa falsificación, y el host retira la firma y vuelve a firmar con la clave real al salir. Una petición que llegue a AWS por cualquier otra vía falla con InvalidSignatureException. La última recomendación de F5 es tratar cada secreto local como comprometido y rotarlo. La línea equivalente en la propia documentación de Bromure dice lo contrario: como solo se filtró la falsificación, la credencial real nunca necesita rotación.

Una entrada de la wordlist nombra un archivo que vale dinero de verdad: terraform.tfstate. Los archivos de estado viven donde vive el repositorio, así que tu lista de carpetas compartidas decide su exposición. Las carpetas del host se enganchan a un workspace como montajes virtiofs en /home/ubuntu/<basename>, con un tope de ocho por workspace, y son la única parte del sistema de archivos de tu Mac a la que la VM llega. Comparte el repositorio en el que trabaja el agente y nada más, y el traversal encuentra un directorio en lugar de un disco.

La mayoría de las historias de seguridad para desarrolladores de este año describen algo que llega: un paquete, un archivo comprimido, un archivo de documentación. Aquí no llega nada. En su lugar, 32.000 peticiones al mes le piden a un proceso que tú arrancaste que le lea archivos en voz alta a un desconocido, y el desenlace depende de qué máquina ejecuta ese proceso y de qué hay en su directorio personal.

Pon el servidor de desarrollo del agente en un switch donde solo esté tu Mac, y llena su directorio personal de sustitutos. Instala Bromure Agentic Coding, y luego deja que los escáneres pregunten.