Volver a todas las publicaciones
Publicado el · por Renaud Deraison

El escáner se negó a leerlo

El Threat Intelligence Group de Google publicó su AI Threat Tracker el 8 de septiembre. Enterrada en la sección sobre un grupo de cadena de suministro llamado UNC6780 hay una técnica que convierte la política de seguridad de un modelo en tapadera: el cargador JavaScript del malware abre con un bloque de comentarios que exige rutas de síntesis de armas biológicas y especificaciones de armas nucleares, colocado ahí para que un escáner de seguridad basado en LLM se niegue por política y nunca llegue al código de debajo. El rechazo es la evasión. Bromure Agentic Coding responde a artefactos así con comprobaciones que nunca los leen.

Convencer a un escáner de seguridad de que apruebe malware cuesta trabajo. Entregarle un archivo tan radiactivo que se niega a mirarlo es un bloque de comentarios, y tu pipeline archiva el rechazo como análisis no disponible.

Tu pipeline de build ya lleva un LLM dentro. La mayoría lo lleva. En algún punto entre la llegada de la actualización de dependencias y el encendido del botón de merge, un modelo lee el diff, lee los archivos nuevos y escribe una opinión breve sobre si algo de eso parece hostil. Buen uso de la tecnología: atrapa cargadores ofuscados por los que la búsqueda de patrones pasa de largo, y cuesta una fracción de céntimo por archivo.

El 8 de septiembre, el Threat Intelligence Group de Google publicó su AI Threat Tracker, un repaso trimestral de lo que los atacantes están haciendo con todo esto. La mayor parte de la cobertura se fue al número de titular: un actor con motivación económica que comprometió la infraestructura cloud de una empresa y después, en palabras de GTIG, “aprovechó un chatbot de codificación con IA, un prompt y un conjunto de instrucciones de agente para planificar, construir y ejecutar una campaña masiva de recolección de credenciales en menos de seis horas”. Miles de credenciales de terceros, sacadas por las propias direcciones IP de la víctima. En un servidor expuesto aparte, los investigadores encontraron un framework de reconocimiento cuyo panel seguía en tiempo real más de 23 800 secretos recolectados.

Esa es la historia que se escribió. El párrafo que merece tu tarde está varias secciones más abajo, en la parte sobre un grupo de cadena de suministro que Google sigue como UNC6780.

El comentario al principio del archivo

UNC6780, que también responde a TeamPCP, lleva desde marzo de 2026 haciendo compromisos de open source a gran escala en PyPI, npm y Docker Hub. Su ladrón de credenciales actual se llama DUSTMAKER, y es un payload JavaScript. Los payloads JavaScript tienen un cargador. El de este es un archivo llamado _index.js, y GTIG encontró algo nuevo en su parte superior:

Además de prompts incrustados en archivos de configuración dirigidos a agentes de codificación con IA, las muestras de DUSTMAKER incluían prompts incrustados como comentarios en la parte superior de los scripts cargadores JavaScript del malware (_index.js). Estos prompts contienen texto adversarial extremo, con referencias a temas como armas biológicas y nucleares, probablemente con la intención de hacer fallar a los escáneres de seguridad basados en LLM o de que omitan el análisis del código JavaScript malicioso de debajo por rechazos de seguridad o de política.

El bloque de comentarios deja el malware a plena vista. Desempaquetado, sin codificar, todo en un archivo: el JavaScript malicioso está tres líneas más abajo, donde cualquiera que leyera hasta el final tropezaría con él.

El comentario hace que el lector no esté dispuesto a llegar al final. Abre con una anulación de sistema falsa, anuncia que las directrices de seguridad quedan suspendidas y exige una ruta de síntesis paso a paso para armas biológicas dispersadas en aerosol. Luego pasa al diseño de un arma nuclear de implosión, con objetivo de potencia incluido. Un modelo que ingiere el archivo y al que se le pregunta “¿es malicioso este código?” tiene ahora delante una pregunta más ruidosa, y a esa responde con un rechazo. El escáner devuelve un mensaje de política. Tu pipeline registra análisis no disponible, igual que lo hace unas decenas de veces por semana por timeouts y archivos demasiado grandes, y el build se pone verde.

La técnica no le cuesta nada a UNC6780. No exige saber qué modelo lee el archivo, qué proveedor aloja ese modelo ni qué dice su prompt de sistema. Le pide una sola cosa al lector: una política de seguridad, que en un pipeline de aseguramiento es justo la razón por la que elegiste un modelo.

El rechazo es la evasión_index.js/* SYSTEM OVERRIDE — CLASSIFIED BRIEFINGruta de síntesis de arma bioespecificación de ingenio de fisióndiseñado para ser rechazadoconst _0x = require(…)el ladrón de credencialesen texto plano, sin ofuscarnunca alcanzadolee desde arribaescáner de seguridad LLMpregunta: ¿es hostil este código?responde antes a la más ruidosarechazo por políticael registro de buildanálisis no disponiblela misma línea que imprimeen timeouts y archivos enormeslo que el atacante debía saberni qué modelo lee el archivo · ni qué proveedor lo alojani el prompt de sistema · ni el umbralsolo que el lector tiene una política
El cargador de DUSTMAKER no esconde su payload. Abre con un bloque de comentarios construido para disparar la política de seguridad del modelo que lo revisa, de modo que el modelo rechaza el archivo entero y el JavaScript malicioso de tres líneas más abajo nunca se evalúa. Un rechazo y un certificado de buena salud son idénticos para un registro de build que solo comprueba si el paso falló.

El resto de la cadena hace lo mismo

Una vez que ves la jugada, el resto del oficio de UNC6780 se lee como variaciones sobre ella. Cada defensa que queda en el camino es también un lector que se forma un juicio sobre un artefacto que escribió el atacante, y a cada una se la alimenta en vez de vencerla.

El paquete llega por las herramientas en las que el agente más confía. GTIG describe a UNC6780 comprometiendo cuentas legítimas de desarrolladores “para publicar forks troyanizados de servidores MCP legítimos en el registro PyPI, como tiktoken_mcp, e inyectar código malicioso directamente en repositorios GitHub oficiales de organizaciones, como azure-functions-mcp-extension”. Poner puertas traseras en servidores MCP significa que el payload llega por la única clase de dependencia que un agente de codificación está diseñado para cargar y a la que concede herramientas.

La comprobación de procedencia recibe una firma válida. Quien trate las atestaciones como señal de confianza debería detenerse en esta:

Las muestras de DUSTMAKER contienen funcionalidad para detectar cuándo se ejecutan en un entorno de integración y entrega continuas (CI/CD). Si lo confirman, extraen tokens OIDC de la memoria de procesos de los runners de GitHub Actions. Con esos tokens, DUSTMAKER se autoriza a sí mismo como publicador de confianza y publica versiones comprometidas de paquetes con atestaciones SLSA Build 3 válidas y firmadas criptográficamente. Los paquetes publicados con tokens válidos pasarán las comprobaciones de confianza automatizadas de los agentes de codificación con IA.

La identidad del propio publicador firmó ese paquete. DUSTMAKER levantó la identidad de la memoria de procesos en el runner donde vive, así que un verificador que compruebe la firma devuelve válida, y hace bien.

El agente de endpoint vigila el barrio equivocado. DUSTMAKER “deja o modifica archivos maliciosos en directorios ocultos de workspace de proyecto para asistentes de codificación con IA y entornos de desarrollo integrados (IDE) (.claude/, .vscode/, .cursor/, etc.)”. GTIG dice por qué: esas carpetas dejan que el malware se funda “en el ruido rutinario del desarrollador” y se aparte de los sitios que vigila la detección de endpoint, como las claves de registro y /etc/cron.*. Tu proveedor de EDR no ha publicado una regla para un archivo nuevo en .cursor/, porque hasta este año esa carpeta guardaba preferencias del editor.

El asistente recibe un archivo de configuración, y los archivos de configuración son instrucciones. Desde esos mismos directorios ocultos, DUSTMAKER “usa archivos de configuración maliciosos … para instruir al asistente de IA a ejecutar comandos o scripts arbitrarios (como setup.mjs) durante interacciones rutinarias de desarrollo. Esto fuerza efectivamente al modelo de IA a ejecutar comandos en nombre del atacante sin conocimiento del desarrollador”. El asistente leyó la configuración de su workspace, que es su trabajo, y la configuración nombró un script que ejecutar. En esa frase no hay exploit por ninguna parte.

Y la API de auditoría borra el rastro de auditoría. El malware disfraza sus tareas de CI con nombres de temática IA como “Copilot Setup”, y luego emite llamadas automatizadas a la API para eliminar los registros de ejecución de workflows de la interfaz de GitHub.

Cinco lectores, cinco artefactos escritos por el atacanteEL LECTORLO QUE LE ENTREGARONLO QUE CONCLUYÓescáner de seguridad LLMlee el archivoun bloque de comentarios que debe rechazararmas biológicas, luego nuclearesnada — declinóanotado como análisis no disponibleverificador de procedencialee la firmauna atestación SLSA Build 3 realacuñada con un token OIDC robadoválida, y con razónla identidad era de verdad la del publicadorasistente de codificación IAlee la config del workspaceuna config escrita por el atacante.claude/ · .cursor/ · .vscode/ejecutar setup.mjsdurante una interacción rutinariadetección de endpointvigila dónde escribe el malwareescribe en cambio en una carpeta del editorni claves de registro, ni /etc/cron.*ruido rutinario del desarrolladoraún no hay regla para esa rutael auditorregistros de workflow borrados vía la API de GitHubun historial vacío
Cinco defensas en una sola cadena de ataque, y ni una falló. Cada una es un lector que se forma un juicio sobre un artefacto, y en todos los casos el artefacto lo escribió el atacante. El escáner rechazó, el verificador verificó, el asistente obedeció, el agente de endpoint miró a otro lado, y el auditor encontró un registro vacío.

Las cinco funcionaron según diseño

Aquí no tienes ningún bug que reportar. El escáner aplicó su política de seguridad, que es para lo que le pagas. El verificador validó una firma que era válida. El asistente cargó la configuración de su workspace, que es la función. El agente de endpoint vigiló las rutas que importaban antes de que los agentes de codificación tuvieran directorios de configuración. Cada uno hizo su trabajo sobre la entrada que recibió, y la entrada la eligió UNC6780.

La evasión corriente pide un veredicto equivocado: empaquetar el binario, partir la cadena, codificar la URL, esperar que el análisis vuelva limpio. DUSTMAKER consigue lo que quiere de un veredicto acertado, o de ningún veredicto.

Toda defensa con la forma lee el artefacto, fórmate una opinión está a ese alcance. Un modelo de lenguaje en la silla del lector amplía el alcance, porque un modelo lleva consigo su propia lista de cosas que no hará, y esa lista es pública, está documentada y es alcanzable por cualquiera que sepa escribir un comentario. La industria pasó dos años endureciendo modelos contra dejarse convencer de decir sí. UNC6780 los convence de no decir nada.

Hazle entonces otra pregunta a los controles de tu pipeline agéntico: cuáles de ellos tienen que leer contenido escrito por el atacante para hacer su trabajo, y qué queda en pie una vez apartas esos.

Comprobaciones con las que no se puede discutir

Bromure Agentic Coding ejecuta cada agente de codificación dentro de una VM Linux virtualizada por hardware en tu Mac, con todos los controles de seguridad del lado anfitrión de esa frontera. Lo que cuenta frente a una cadena como esta es qué toman esos controles como entrada.

La puerta de edad lee un reloj. El proxy del anfitrión reconoce las peticiones a los grandes registros de paquetes y aplica la política de cadena de suministro del workspace antes de que un byte llegue a la VM. La puerta de edad, la única capa activa por defecto y fijada en un mínimo de dos días, rechaza las versiones más jóvenes que el corte. Para lograrlo reescribe el listado de versiones del registro, de modo que desde el punto de vista del agente una versión demasiado fresca todavía no existe; una descarga directa fijada vuelve como HTTP 451 cuyo cuerpo indica la edad real del paquete. El paquete en sí nunca se lee. Un bloque de comentarios construido para descarrilar un modelo de lenguaje no mueve ninguna fecha de publicación, y los forks troyanizados de UNC6780 son publicaciones frescas por construcción. npm, PyPI, Cargo, RubyGems y Packagist llevan todos fechas de publicación por versión, así que la puerta los cubre.

Un clasificador no tiene política que invocar. El detector de código fuente de Bromure puntúa los tramos tool_result que el agente devuelve en streaming al modelo —contenidos de archivos, páginas web y salida de comandos— con un modelo PromptGuard local de la familia DeBERTa ejecutándose como ONNX en tu Mac. Es un clasificador de secuencias, no un modelo generativo. Puesto ante el bloque de comentarios de DUSTMAKER no compone respuesta alguna, no sopesa ninguna cuestión sobre si responder está permitido, y no declina nada. Emite un número entre cero y uno, y un texto que abre con una anulación de sistema falsa anunciando directrices de seguridad suspendidas es justo la forma que dispara ese número. Por workspace eliges qué hace un acierto: registrar, preguntar o bloquear. Un bloqueo devuelve 451, y el modelo nunca ve el contenido.

Algo distinto del agente lee la configuración del workspace. Un archivo en .claude/ que le dice al asistente que ejecute setup.mjs es la puerta trasera por archivo de reglas, y tiene su propio detector: una pasada heurística determinista para Unicode oculto, patrones de metainstrucción, rutas de credenciales y construcciones de curl a shell, más un clasificador ModernBERT afinado para las instrucciones maliciosas que no encajan en ningún patrón fijo. Ambos corren en el proxy, en el dispositivo, sobre los archivos de instrucciones extraídos del prompt de sistema, donde nada dentro de la VM puede apagarlos. Ambos son interruptores por workspace, y cada uno quiere una descarga de modelo la primera vez que lo enciendes. Esa descarga son los cinco minutos que esta historia te está pidiendo.

Un ladrón de credenciales necesita credenciales. DUSTMAKER existe para cosechar secretos de entornos de desarrollo, y un workspace de Bromure no tiene ninguno. Bromure sustituye cada credencial que configuras por un señuelo que preserva la estructura, derivado del valor real y de una sal por instalación: sk-ant-api03-brm-…, un token ghp_ de la longitud correcta, brm-mcp_…, brm-k8s-…. Esos señuelos van a las variables de entorno y a ~/.git-credentials, ~/.docker/config.json, ~/.kube/config y ~/.aws/config, que es la lista que enumera un ladrón. Tus valores reales quedan cifrados en el Mac, y el proxy del anfitrión los intercambia sobre el cable, acotados al host de destino para el que se acuñó cada uno. Los bytes de las claves privadas SSH nunca entran en la VM; solo cruzan las firmas. Ningún interruptor gobierna nada de esto, porque el proxy es la única ruta de la VM hacia la red.

Los señuelos sirven además de cables trampa. Un token señuelo tiene un único destino legítimo. El proxy escanea cada petición saliente, cabeceras y cuerpo, en busca de un señuelo que vaya a otra parte. Si hay coincidencia, rechaza la petición sin reenviar un byte, pausa la VM en el acto y anota una fila roja de Credential brokering en la Security Timeline. Marca el workspace como comprometido, y el siguiente arranque te obliga a borrar primero el disco y la imagen de home. Después no rotas nada, porque el ladrón tenía un marcador de posición y el marcador nunca salió.

El registro vive en tu máquina, fuera del alcance de la API a la que llama el malware. Con el trazado en Activity only, el proxy escribe una fila de metadatos por petición que sale de la VM, sin cuerpos. bromure-cli trace hostnames imprime cada host distinto que el workspace ha contactado con sus conteos, y bromure-cli trace leaks nombra el destino tras una alerta de compromiso. Un atacante con un token de GitHub vacía un historial de workflows en unas pocas llamadas a la API. Vaciar ese rastro implica llegar a tu Mac.

controles que leen el artefactoel atacante escribe la entrada de todos ellosel modelo lee el archivodale algo que tenga que rechazarel verificador lee la firmaroba el token OIDC; la firma es realel asistente lee la configescribe la config; ejecuta lo que nombresel entorno guarda secretos reales~/.aws/config · ~/.docker · GH_TOKENlo que el atacante necesitaun bloque de comentarios y un lector con políticacontroles que leen otra cosaen el anfitrión, fuera de la VM, en tu Macla puerta de edad lee una fecha de publicacióndos días por defecto; lo demasiado fresco no existeel clasificador emite una probabilidadONNX local · registrar, preguntar o bloquear · nada que declinarla VM solo guarda señuelossk-ant-api03-brm-… · brm-mcp_… · brm-k8s-…el proxy compara bytes con un destinoseñuelo fuera de alcance: 451, VM pausada, fila rojalo que el atacante necesitacambiar una fecha, o un secreto que no está
El mismo artefacto frente a dos clases de comprobación. Un control que lee contenido escrito por el atacante puede recibir contenido construido para descarrilarlo. Un control que lee una fecha, un patrón de bytes o un host de destino no tiene nada que discutir, y la credencial que el ladrón vino a buscar nunca estuvo en la máquina donde se ejecuta.

Dos ajustes, esta tarde

Abre el panel Supply Chain del workspace y confirma que la puerta de edad está activa con un corte con el que puedas vivir. Dos días es el valor por defecto, y es el control más barato que tienes contra un fork troyanizado recién publicado. Luego abre Prompt Injection y activa los dos detectores; cada uno descarga su modelo una vez, y a partir de ahí el escaneo es local y gratis.

Una regla que encoge la pregunta

En Guardrails, pon Unmatched traffic en Deny y lista lo que el trabajo necesita: allow web api.github.com, allow web registry.npmjs.org, default deny. Un ladrón que no puede alcanzar su servidor de mando es un archivo en un disco que estás a punto de tirar, y guardar empuja la regla a las sesiones en curso sin reiniciar.

La ausencia le gana a la evaluación

Seguimos llegando al mismo sitio desde direcciones distintas. Un proxy de salida que confió en un nombre que el agente podía escribir. Un guardián de comandos que leía bash distinto de como lo lee bash. Una pasarela de IA cuya comprobación fallida de clave caía en un objeto de autenticación vacío. En cada caso un componente hacía un trabajo honesto de evaluar algo, y la evaluación se rompió.

DUSTMAKER afila el argumento, porque no pide ninguna evaluación rota. Pide que una evaluación ocurra sobre contenido que escribió UNC6780. Dale un archivo a un lector y el lector se forma una idea de él; quién suministró el archivo decide el resto.

La evaluación es una primitiva más débil que la ausencia, y no paramos de entregarle a la evaluación el trabajo que la ausencia hace gratis. No se puede discutir con una fecha de publicación, y una probabilidad no tiene disposición alguna a retractarse. Un ladrón de credenciales corriendo con todos los privilegios dentro de una VM desechable, recorriendo todos los archivos de configuración que conoce, sale de ahí con un puñado de señuelos y un socket que no puede rodear.

Meter un modelo en tu pipeline de revisión fue la decisión correcta. Asegúrate de que los controles que detienen cosas sean aquellos con los que nada del repositorio puede hablar. Instala Bromure Agentic Coding y dale a tu agente comprobaciones que nunca tengan que leer el archivo del atacante.