Volver a todas las publicaciones
Publicado el · por Renaud Deraison

La documentación nombraba un paquete que no era de nadie

Unos investigadores escanearon 6.214 dominios corporativos en busca de llms.txt, el archivo legible por máquina que las empresas publican hoy para los agentes de IA, y encontraron 237 paquetes y dominios nombrados en esos archivos que nadie había registrado jamás. Reclamaron unos cuantos. El primer agente de programación dentro de una red Fortune 500 instaló uno en menos de cuatro minutos. El atacante no tocó ningún mantenedor, ningún registro ni ningún repositorio. La barrera de edad de Bromure Agentic Coding viene activada de fábrica en dos días, y un nombre que un atacante reclama es, por construcción, una publicación recién salida.

Un proveedor escribió en su propia documentación un comando de instalación para un paquete que nunca publicó. El nombre quedó ahí, libre, a disposición de cualquiera. Los agentes de programación leyeron la documentación, la creyeron y ejecutaron la instalación.

En algún momento de los dos últimos años, sus herramientas de desarrollo empezaron a distribuir un archivo nuevo. Está junto a robots.txt, se llama llms.txt y contiene un resumen en texto plano del producto, escrito para una máquina y no para una persona. Qué hace la API. Qué endpoints importan. Qué paquete instalar para empezar. Los agentes de programación lo descargan y, como viene del dominio del propio proveedor, lo tratan como un hecho consumado.

Un investigador llamado Alon Hertz, que trabaja bajo el nombre PANDEX, fue a ver qué le dicen esos archivos a un agente que haga. Su método tiene tres pasos y ningún exploit: bajar llms.txt y llms-full.txt de los directorios de empresas, lanzar agentes de verdad contra ellos en entornos aislados y mirar a qué se lanzan los agentes, y después comprobar cada nombre de paquete y cada nombre de host que salió de ahí contra npm, PyPI y los registros de dominios. Describe las herramientas en whatwouldai.do, bajo un lema que resultó ser el hallazgo: los agentes actuarán sobre cualquier cosa.

El escaneo cubrió 6.214 dominios activos, tomados de empresas de la Fortune 500, grandes tecnológicas, fintech y contratistas de defensa, y más de 8.000 de estos archivos. En 120 de ellos, lo que el archivo le mandaba instalar al agente no existía. En todo el corpus, 237 nombres de paquetes y de hosts nombrados estaban libres: disponibles en npm, PyPI, RubyGems, NuGet, crates.io y Packagist, o libres como subdominios en Vercel, Render, Fly y Netlify. Cualquiera podía tomarlos por el precio de un registro.

Así que los investigadores tomaron un puñado, publicaron paquetes inofensivos bajo esos nombres con una baliza que llamaba a casa al instalarse, y esperaron.

La primera llamada llegó en menos de cuatro minutos. La segunda, dentro de la hora. A lo largo del periodo siguiente registraron instalaciones desde decenas de organizaciones, varias de ellas entornos Fortune 500, provocadas por agentes que corrían sobre Claude de Anthropic, Codex de OpenAI y Hermes de Nous Research. Bruce Schneier recogió el estudio el 4 de septiembre y lo leyó como merece: la línea entre datos y código ejecutable se está desmoronando. Un investigador citado en esa entrada lo dice con menos palabras. El modelo de confianza está roto. Los agentes tratan la documentación del proveedor como verdad de campo y no la cuestionan.

Clerk documentó un paquete que nunca entregó

El ejemplo más claro viene de un proveedor de autenticación, cuya documentación le dice al lector que ejecute:

npx clerk-next-fix-auth-protection

Clerk nunca publicó un paquete independiente con ese nombre. Quizá el comando era una abreviatura, quizá apuntaba a algo interno, quizá un redactor se equivocó. El motivo da igual, porque para un agente el nombre se leía como real. Otra persona lo registró. Las bases de avisos de seguridad recogen ahora ese paquete como MAL-2026-11069, e hizo lo que se construye para reconocimiento: los hooks de instalación de npm se dispararon y enviaron fuera el nombre de la cuenta que instalaba, el nombre de host de la máquina, el directorio de trabajo y una marca de tiempo.

Eso es un mapa del entorno de compilación de alguien, recogido dentro de su red por su propio agente de programación, que estaba siguiendo la documentación de su propio proveedor.

Cinco pasos corrientes, ningún compromiso en la cadena1 · el proveedor publicavendor.com/llms.txtescrito para agentes, no personasnpx clerk-next-fix-auth-protection2 · el nombre está librenunca se publicó ningún paquetecon ese nombre237 nombres así en 120 archivos3 · alguien lo reclamauna publicación npm corrienteMAL-2026-11069la carga vive en los hooks de install4 · el agente lee la doc y la creese vio a Claude, Codex y Hermes ejecutar la instalaciónel comando venía del dominio del propio proveedor, no había nada que dudarsin credencial robada · sin repo comprometido · sin mantenedor phishado5 · los hooks llaman a casausuario · nombre de hostdirectorio de trabajo · marca de tiempoun mapa del entorno de buildEl único trabajo real del atacante fue leer un archivo que el proveedor de la víctima publicó a propósito.
La cadena no tiene ningún compromiso dentro. Un proveedor publica un documento legible por máquina que nombra un comando de instalación; el paquete nunca se publicó, así que el nombre sigue libre en el registro; un atacante lo registra; el agente descarga el documento como verdad de campo y ejecuta la instalación; los hooks de instalación informan del entorno de vuelta. Cada eslabón es una operación normal ejecutada correctamente.

Por qué el consejo de siempre apunta justo a la carga

Toda guía de cadena de suministro le dice que compruebe el nombre de un paquete contra la documentación oficial. Ese consejo falla aquí, porque la documentación oficial es exactamente de donde salió el nombre malo.

Invierta el problema del que escribimos en julio y obtiene este. Allí un agente alucina un nombre de paquete plausible y un atacante registra los nombres que suele inventarse, así que el paso débil es la conjetura del modelo. Aquí el modelo no conjetura nada. Va al proveedor, lee lo que el proveedor dice, lo sigue, y es esa secuencia cuidadosa la que lo atrapa.

Un compromiso de registro también tiene otra forma. Cuando un flujo de release publicó diez versiones envenenadas de un paquete real el mes pasado, algo tenía que romperse primero: un flujo que confiaba en un comentario. Aquí no se rompe nada. Un redactor técnico teclea un comando que no encaja del todo con un artefacto publicado, que es el error más corriente del software, y el hueco entre el nombre y el artefacto se convierte en una primitiva de ejecución que cualquiera en internet puede recoger.

Lo que halló el escaneo, y lo rápido que llegó la prueba6.214dominios activos escaneadosFortune 500, big tech, defensa8.000+docs dirigidas a agentesllms.txt · llms-full.txt120archivos que citan algoque nunca se registró237nombres libres, 6 registrosmás Vercel, Render, Fly, Netlifyluego registraron una muestra y esperaronprimera llamada de instalación desde un entorno Fortune 500menos de 4 minutossegunda llamadadentro de la hora · decenas de organizaciones
El embudo del estudio. De 6.214 dominios corporativos con documentación dirigida a agentes, 120 archivos nombraban algo sin registrar, que suman 237 nombres libres. Registrar una muestra de ellos produjo una llamada de instalación desde dentro de una red Fortune 500 en menos de cuatro minutos.

La capa que atrapa esto ya está encendida

Bromure Agentic Coding ejecuta cada agente de programación en una VM Linux virtualizada por hardware en su Mac, y cada paquete que el agente descarga pasa por un proxy en el lado anfitrión de esa frontera, antes de que un solo byte llegue a la VM. Allí se aplican varias capas. Para esta historia, una de ellas hace el trabajo por sí sola, y es justo la que un espacio de trabajo nuevo ya trae encendida.

La barrera de edad convierte un nombre recién reclamado en un no-evento. Un espacio de trabajo rechaza versiones de paquete más jóvenes que un corte, fijado en dos días de fábrica. El proxy lo impone reescribiendo el listado de versiones del registro al pasar: elimina las versiones más jóvenes que el corte y vuelve a apuntar latest a la superviviente más reciente. Un paquete publicado por primera vez esta mañana no tiene ninguna versión lo bastante vieja para sobrevivir a ese filtro, así que desde el punto de vista del agente no hay nada que instalar en ese nombre. Si el agente pide directamente una versión demasiado fresca, la descarga vuelve como un HTTP 451 cuyo cuerpo indica la edad real del paquete y el mínimo exigido, y npm imprime ese texto tal cual.

Lea el ataque otra vez con eso puesto. Toda la técnica depende de que un nombre pase de no publicado a publicado en el momento que elija el atacante. Esa transición es una publicación recién salida. La única propiedad que el atacante no puede evitar es aquella sobre la que se apoya la política por defecto.

El recorte de scripts de instalación elimina exactamente el mecanismo que usó la carga. El paquete observado hacía su trabajo en los hooks preinstall, install, postinstall y prepare de npm. Con el recorte de scripts activado, el proxy descomprime cada tarball de npm al vuelo, borra esas cuatro claves del package.json, limpia dist.integrity y dist.shasum de los metadatos para que la propia comprobación de hash de npm siga pasando, y vuelve a comprimir. El paquete se instala; la baliza no tiene desde dónde correr. Los paquetes que necesitan sus hooks, como los compiladores de bindings nativos tipo better-sqlite3, entran en una lista de permitidos por espacio de trabajo y los conservan.

El filtrado por reputación conoce este paquete por su nombre. MAL-2026-11069 es un aviso de malware. Con socket.dev elegido como filtro de paquetes del espacio de trabajo, su bloqueo de paquetes comprometidos se dispara ante señales de malware y de typosquat en cualquier severidad; la comprobación gratuita de OSV cubre los ocho ecosistemas interceptados sin clave de API. Cuando una fuente de reputación no puede emitir un veredicto, ya sea por limitación de tasa, por una red caída o por un ecosistema que no cubre, Bromure falla en cerrado en lugar de permitir la descarga. La descarga se pausa y le pregunta a usted, paquete a paquete y versión a versión, de modo que un atacante capaz de provocar un fallo de consulta no gana nada.

El reconocimiento que recoge pertenece a la VM. Todos los titulares sobre este estudio dicen lo mismo: los agentes están instalando código desconocido en redes corporativas. La cosecha de la baliza fue el nombre de usuario que instalaba, el nombre de host de la máquina y el directorio de trabajo. En un espacio de trabajo de Bromure esos tres campos describen un invitado Linux en su Mac, y no un host de compilación unido al dominio con un nombre interno que merezca entrar en una lista de objetivos. Las credenciales que hay en esa VM son señuelos cuyos valores reales solo existen en la memoria del proxy anfitrión. Si un código hostil intenta llevar una de ellas a un host para el que no fue acuñada, el proxy bloquea la petición y pausa la VM en pleno vuelo.

La denegación por defecto responde a la otra mitad del estudio. Buena parte de los nombres sin reclamar eran nombres de host y no paquetes: dominios caducados y subdominios gratuitos en Vercel, Render, Fly y Netlify, sentados en documentación que los agentes descargan. El cortafuegos de salida del espacio de trabajo es una tabla ordenada de reglas con un ajuste Unmatched traffic; póngalo en Deny y la VM abre conexiones hacia los hosts que usted haya listado y hacia nada más, en cualquier protocolo. Dos componentes lo imponen fuera del invitado: el conmutador de red virtual casa cada flujo por la dirección IP de destino, y el proxy anfitrión vuelve a casar por el nombre de servidor TLS. Una baliza apuntada a un dominio que alguien registró el martes pasado no sale de la máquina. Las ediciones de reglas alcanzan a las sesiones en marcha sin reiniciar.

Cada intento se lleva una fila. En el nivel de traza Activity only, el proxy escribe un registro de metadatos por cada petición que sale de la VM, con marca de tiempo, host, puerto, método, ruta, estado, latencia y bytes, y no almacena ningún cuerpo de petición. bromure-cli trace hostnames my-workspace imprime cada host distinto que el espacio de trabajo contactó, con sus recuentos. Usted ve un dominio de retorno que ninguno de sus ingenieros reconoce el mismo día en que ocurre, en su propia máquina, en lugar de leerlo meses después en el conjunto de datos de otro.

Un agente en una máquina de desarrollonpx clerk-next-fix-auth-protectiondirecto de la doc del propio proveedorlo que pasa después, todo dentro de la redel registro sirve lo que se publicó, por nuevo que seapostinstall corre con la identidad del desarrolladorusuario, host y cwd son hechos corporativosla llamada de retorno va adonde quiereningún registro por petición del destinola instalación funciona y se ve igual que todaslas otras que el agente corrió esa tardePrimera llamada: menos de cuatro minutos.Se entera cuando un desconocido publica el estudio.Un espacio de trabajo de Bromurenpx clerk-next-fix-auth-protectionel mismo comando, de la misma doclo que pasa después, todo en el hostbarrera de edad: fuera las versiones de menos de dos díasun nombre tomado hoy no tiene nada instalablestrip: se quitan los cuatro hooks de instalaciónusuario, host y cwd describen una VM Linuxsalida sin coincidencia denegada, por IP de destinoel rechazo es un 451 que el agente puede leer, y unafila en el Security Log con la edad real del paquetePrimera llamada: ninguna.Se entera desde su propia máquina, el mismo día.
A la izquierda: la instalación corre en una máquina de desarrollo dentro de la red corporativa, se consulta el registro directamente, los hooks de instalación se ejecutan con la identidad del desarrollador, y la llamada de retorno se va sin dejar constancia. A la derecha: el mismo comando, pasado por un proxy del lado anfitrión que filtra la versión recién publicada fuera de los metadatos, quita los hooks si llega hasta ahí, deniega la salida sin coincidencia, y escribe una fila por cada petición.

Lo que ya está encendido

Un espacio de trabajo nuevo viene con la barrera de edad habilitada en un mínimo de dos días, y esa es la configuración con la que choca este ataque. Súbala si su trabajo tolera la espera, hasta 90 días en el selector, y use Exempt packages para los que su propio equipo publica e instala en la misma hora, con la forma npm:nuestro-paquete.

Lo que vale la pena encender hoy

En el panel Supply Chain del espacio de trabajo, habilite el recorte de scripts de instalación y elija un filtro de paquetes. En Guardrails, ponga Unmatched traffic en Deny y liste los hosts que el trabajo necesita. En Tracing, Activity only guarda el rastro de hosts contactados y nada del contenido. Cada una de esas ediciones se aplica a sesiones que ya están corriendo.

Nadie corrige un archivo escrito para máquinas

El escáner de PANDEX caza una cosa más. Junto a los paquetes y dominios sin reclamar, busca instrucciones dirigidas al agente en vez de al lector: directivas plantadas en el archivo, a veces escondidas con trucos de Unicode y caracteres de ancho cero. La tubería de compilación de un proveedor genera llms.txt, su agente lo consume, y nadie en ninguna de las dos empresas lee lo que pasó entre ellas.

Bromure trata una página descargada como entrada no confiable. El contenido que el agente trae de fuera, ya sea contenido de archivos, páginas web, cuerpos de incidencias o salida de comandos, llega al modelo como un span tool_result. Con el detector de código fuente habilitado, un modelo PromptGuard que corre en su Mac puntúa esos spans en el cable antes de que el modelo actúe sobre ellos, y usted elige si registra, pregunta o bloquea. Ningún contenido va a ninguna parte para ser analizado.

Hemos pasado una década aprendiendo a verificar artefactos: firmar el paquete, fijar el hash, atestiguar la compilación. Este estudio va del paso anterior a todo eso, aquel en el que un agente decide qué artefacto pedir, trabajando a partir de un documento que no lleva firma alguna y que escribió un redactor técnico con prisa. La documentación se ha convertido en una entrada ejecutable, y los registros están llenos de nombres libres esperando a que alguien se dé cuenta de que un proveedor los mencionó.

Pásele un grep a su propio llms.txt buscando nombres que nunca publicó. Es el trabajo de una mañana y lo saca de la lista de objetivos de alguien. Después dele a su agente una máquina donde un paquete publicado hace cuatro minutos no sea instalable, y donde usted pueda ver todos los sitios adonde fue. Instale Bromure Agentic Coding y ponga la ruta de instalación en el anfitrión.