La flag dijo que no
El 3 de agosto de 2026, The Hacker News cubrió FaceHugger: tres fallos en la biblioteca Diffusers de Hugging Face — siete millones de descargas al mes — que permiten a un repositorio de modelo manipulado ejecutar código arbitrario en la máquina que lo carga, incluso con trust_remote_code=False activado. La causa raíz: la comprobación de confianza corría en un momento distinto al de la carga del código. Los agentes de código traen modelos igual que traen paquetes, así que la flag que mentía custodiaba laptops de desarrolladores. Bromure Agentic Coding pone la frontera bajo el proceso en lugar de dentro de él: la carga corre en una VM desechable, las credenciales al alcance son señuelos, y la llamada a casa del payload muere en el proxy del host — el repositorio malicioso le cuesta al atacante su payload y a ti no te cuesta nada.
El repositorio contenía un archivo llamado None.py, y ese era todo el
truco. La flag de seguridad estaba activada, la comprobación corrió, y
el código se ejecutó de todos modos, porque la comprobación y la carga
ocurrían en dos momentos distintos — y el atacante solo tenía que estar
presente en el segundo.
El 3 de agosto de 2026, The Hacker News
recogió la investigación que Zafran Labs publicó a finales de julio:
tres vulnerabilidades en la biblioteca Diffusers de Hugging Face,
bautizadas colectivamente FaceHugger, que permiten a un repositorio de
modelo manipulado ejecutar código Python arbitrario en cualquier máquina
que lo cargue. Diffusers es la vía estándar para ejecutar modelos de
difusión — unos siete millones de descargas al mes, asentados en pipelines
de producción, jobs de CI, notebooks e imágenes de contenedor. La
biblioteca tiene una salvaguarda para exactamente este escenario:
trust_remote_code, una flag que se supone impide que código sin revisar
de un repo de modelo corra en tu máquina. Los tres fallos pasan por
delante de ella mientras está en False.
La comprobación y la carga eran dos eventos distintos
Cargar un modelo desde el Hub no es una sola operación. La biblioteca hace
dos peticiones HTTP secuenciales: la primera trae la configuración del
repositorio, la segunda trae el código y los pesos que esa configuración
señala. La puerta trust_remote_code hacía su inspección durante la
primera petición. Aquello contra lo que montaba guardia llegaba en la
segunda.
Los investigadores de Zafran encontraron tres caminos por esa brecha,
ya con CVE asignados
y parcheados en Diffusers 0.38.0. En CVE-2026-44827 (CVSS 8.8), una
rareza del formateo de cadenas construye el nombre de archivo None.py
cuando no se solicita ningún pipeline custom — y la comprobación de
existencia de ese archivo corre por una ruta de código que no lo señala,
de modo que un repositorio que incluye un archivo llamado None.py cruza
la puerta y se ejecuta al cargar. CVE-2026-45804 es una carrera: cerca de
un tercio de segundo separa las dos peticiones, y un repositorio que
cambia entre ambas se valida como una cosa y se ejecuta como otra.
CVE-2026-44513 se salta la puerta en cargas de snapshots locales. Los
investigadores de Zafran comprimieron los tres en una frase: cualquier
método que haga que el cargador vea código custom que la puerta no vio
permite el bypass.
El patrón tiene historia. Transformers — el otro pilar del ecosistema de
Hugging Face — tuvo su propio bypass de trust_remote_code=False
(CVE-2026-4372), explotable mediante una llamada estándar de carga de
modelo. La clase de bug viene con el diseño: una promesa de seguridad que
vive dentro del proceso que vigila. La flag es un argumento de función,
mientras que la aplicación está repartida entre rutas de código, formateo
de nombres de archivo y timing de peticiones. Cuando cualquiera de ellos
discrepa de la flag, la flag pierde, y nada te dice que perdió. El modelo
carga y la demo funciona.
Tu agente carga modelos un martes cualquiera
Lee esa historia desde la silla de un agente de código. Trae el pipeline
de inpainting del Hub y haz un benchmark contra el nuestro es una tarea
normal de martes. El proyecto fija diffusers==0.37, porque los proyectos
fijan cosas. El agente escribe lo que escribiría un ingeniero cuidadoso —
from_pretrained(..., trust_remote_code=False) — y la versión cuidadosa
es la versión vulnerable. Puedes leer el código fuente de un paquete antes
de instalarlo; un repo de modelo es config, pesos y, gracias al cargador,
lo que sea que diga la segunda petición HTTP.
Los agentes han puesto caliente esa ruta de carga. Traen modelos igual que traen paquetes, en mitad de la tarea y por nombre, y los atacantes ya descubrieron que la propia recomendación puede ser envenenada. Un repo de modelo que detona al cargar es la misma jugada un registro más allá, con mejor disfraz: el payload se aloja en un artefacto que archivas como datos, no como código.
El consejo de Zafran es tratar los repositorios de modelos como código no confiable. Correcto — y tiene un prerrequisito en el que el informe no se detiene: un lugar donde el código no confiable pueda correr sin costarte nada. Una política de sospecha es una disciplina que mantienes hasta que una fecha límite se pone ruidosa. Una sala construida para la detonación no necesita tu disciplina.
La carga ocurre dentro de la caja
Bromure Agentic Coding es esa sala, y empieza donde falló la flag de FaceHugger: la frontera se asienta debajo del proceso, en un entorno con el que las entrañas del cargador no pueden discutir, aplicada coopere o no el proceso.
Recorre el ataque a través de un perfil. Tu agente toma la tarea del
benchmark, pins incluidos, y llama al cargador sobre el repositorio
malicioso. None.py se ejecuta — dentro de una VM Linux desechable, a un
hipervisor de macOS. La máquina que el payload ahora posee es una máquina
que existe para esta tarea, contiene el workspace de esta tarea y se
descarta cuando el trabajo se mergea.
El primer movimiento real del payload es hacia fuera. Tiene que serlo:
traer una segunda etapa, o enviar lo que encontró a su operador. Cada una
de esas peticiones cruza el proxy del host, donde el destino se comprueba
contra la lista que aprobaste para este perfil. huggingface.co está en
esa lista — así llegó el modelo. El buzón del atacante no lo está. El
cable se niega, como debería haberse negado la
puerta — salvo que esta negativa
no depende de que las rutas de código de la biblioteca estén de acuerdo
entre sí. El proxy no sabe ni le importa qué archivo Python hace la
petición. Comprueba el destino, no encuentra nada aprobado y corta la
conexión.
Supón que el payload se salta la red y se va de compras, por variables de entorno y archivos de claves. Encuentra los señuelos del broker. Los valores reales viven en el host y se inyectan en el cable, solo para destinos aprobados — las contraseñas nunca estuvieron en la VM para ser robadas. Y si intenta hacer permanente su estancia — reescribir una config, apagar algo — las formas destructivas se pausan ante un diálogo de guardrail en el host, y la persistencia en una máquina con una vida útil de una tarea es, de todos modos, un mal negocio.
Mientras tanto, la tarea sale bien. El benchmark corre, los números vuelven, tú lees el diff. El atacante gastó una posición de cadena de suministro en un payload que se ejecutó en una sala sin nada que robar y sin forma de llamar a casa. Tú no gastaste nada — ni siquiera atención, a menos que disfrutes leyendo logs de proxy por deporte.
Parchea la biblioteca, quédate con la sala
La nota de corrección del informe de Zafran dice a las organizaciones que actualicen y luego salgan de caza: aplicaciones, notebooks, servicios de inferencia e imágenes de contenedor que fijan versiones antiguas de Diffusers. Esa caza no tiene línea de meta — en algún lugar de tu árbol de dependencias, algún proyecto fija la versión con la brecha, y el próximo FaceHugger será la flag de otra biblioteca con otro bypass. El guardia y el shell discreparon fue este fallo en una lista de comandos permitidos; el sandbox era una frase fue este fallo en un prompt. En cada caso, una comprobación vivía dentro de la cosa que comprobaba, y la cosa ganó.
La frontera de un perfil no comparte ese destino, porque no participa en
el proceso que contiene. La VM no parsea configs de pipeline. El proxy no
tiene una ruta de código None.py. Su único trabajo — esta máquina es
desechable, este cable rechaza destinos fuera de la lista — es el mismo el
día en que se publica un bypass que el día en que se parchea. Un CISO de
Sumo Logic, comentando esta investigación sin pensar en Bromure, nombró
las defensas que aguantan: las poco glamurosas: control de egreso,
segmentación e higiene de credenciales.
Nosotros construimos las poco glamurosas dentro de un perfil que tú
clicas.
Sigue trayendo modelos — ahí está la palanca, y tu agente debería probarlos por ti. Deja de permitir que un argumento de función sea lo único entre el Hub y tu laptop. Instala Bromure Agentic Coding, dale al agente un perfil cuyas reglas aguanten en el cable, y la próxima vez que la promesa de un cargador se rompa — y alguna se romperá — la noticia seguirá siendo de otro.