Volver a todas las publicaciones
Publicado el · por Renaud Deraison

Revisaste los cuarenta minutos equivocados

El 14 de agosto de 2026, SOCRadar publicó una reatribución del compromiso de la cadena de suministro de LiteLLM: el 95 % de las organizaciones afectadas ya había sido cosechado días antes, por el propio escáner Trivy de Aqua Security. Cinco meses de respuesta a incidentes se dimensionaron sobre la ventana equivocada, porque cada control de la cadena necesitaba que alguien nombrara primero el paquete malicioso. Las capas de cadena de suministro y de credenciales de Bromure Agentic Coding no necesitan el nombre.

Te dijeron que revisaras si habías instalado un paquete de Python durante una ventana de cuarenta minutos el 24 de marzo. Cinco meses después, SOCRadar descubrió que el 95 % de las víctimas ya había sido vaciado cinco días antes, por su propio escáner de vulnerabilidades.

El 14 de agosto, Ionut Arghire, de SecurityWeek, publicó la reatribución de SOCRadar de uno de los mayores compromisos de infraestructura de desarrollo del año. SOCRadar tenía el conjunto de datos a nivel de registro, con los registros de recolección por organización de 2.188 entidades, y planteó una pregunta que nadie había hecho: ¿cuándo salieron los datos de cada organización?

Para 2.085 de ellas, la recolección había terminado antes de que se publicaran los paquetes maliciosos sobre los que se advirtió a todo el mundo. Eso es el 95 %. La conclusión de SOCRadar cabe en una frase:

“Esa cronología encaja con el compromiso aguas arriba de Trivy, no con la ventana de instalación de LiteLLM. Los 40 minutos que todos reportaron fueron el acto final, no la obra entera.”

Los investigadores desarmaron la carga útil ya en marzo. SOCRadar cambió la línea de tiempo. Durante cinco meses la industria condujo su respuesta a incidentes contra la ventana equivocada, con controles que no podían hacer nada hasta que alguien nombrara la cosa mala.

El acto final

Los famosos cuarenta minutos pertenecen a LiteLLM. El 24 de marzo, las versiones 1.82.7 y 1.82.8 subieron a PyPI a las 10:39 UTC y duraron unos cuarenta minutos antes de que PyPI las pusiera en cuarentena. Las recomendaciones de entonces decían tratar como sospechosa cualquier instalación hecha hasta las 16:00 UTC de ese día.

La carga útil merece un momento por lo que derrota. Es un archivo .pth, litellm_init.pth, dejado en site-packages. Python ejecuta los archivos .pth al arrancar el intérprete, importe o no algo el paquete que los instaló. No hay setup.py que inspeccionar ni script de instalación que quitar, así que --ignore-scripts y todo control construido sobre el modelo de ejecución de scripts de instalación pasan de largo. The Hacker News cubrió el mecanismo el 12 de agosto y anotó el identificador a nivel de ecosistema, CVE-2026-33634. La cosecha iba a models.litellm[.]cloud.

Los atacantes subieron esos wheels con un token de publicación de PyPI robado, saltándose la propia canalización de release de LiteLLM. Entonces, ¿de dónde salió ese token?

La obra entera

Trivy. El escáner de vulnerabilidades de código abierto de Aqua Security, que decenas de miles de canalizaciones ejecutan para encontrar exactamente este tipo de problema.

El aviso de Aqua, GHSA-69fq-xp46-6x23, no suaviza la causa raíz. Un actor rastreado como TeamPCP (UNC6780 para Google) extrajo un token de acceso personal del entorno de GitHub Actions de Trivy a través de un workflow pull_request_target, a finales de febrero. Aqua lo divulgó el 1 de marzo y rotó las credenciales. El aviso dice después que la rotación no fue “atómica (no todas las credenciales se revocaron simultáneamente)”. El atacante se instaló dentro de la rotación y fue recolectando los nuevos secretos a medida que Aqua los emitía.

Diecinueve días después los gastó. El 19 de marzo hizo force-push de 76 de las 77 etiquetas de versión de aquasecurity/trivy-action, y de las siete etiquetas de aquasecurity/setup-trivy, sobre commits maliciosos, de modo que un workflow fijado en @v0.28.0 traía código nuevo sin que nada pareciera cambiar en el repositorio. Usando la cuenta de servicio comprometida aqua-bot, publicó Trivy v0.69.4 en GHCR, ECR Public, Docker Hub, deb y rpm. Las imágenes v0.69.5 y v0.69.6 de Docker Hub siguieron el 22 y 23 de marzo, sobre una credencial comprometida por separado.

El desglose de StepSecurity describe lo que hacía el ladrón inyectado en cuanto una canalización lo ejecutaba:

  • Leer el entorno de cada proceso del runner a través de /proc/*/environ.
  • Volcar la memoria del proceso worker del runner de GitHub Actions vía /proc/<pid>/mem, usando Python codificado en base64. Eso recupera los secretos que un runner enmascara en sus logs, ya que el enmascaramiento es un filtro de log y la memoria del proceso guarda los valores sin enmascarar.
  • Barrer el sistema de archivos en busca de claves privadas SSH, credenciales de git, tokens de AWS, GCP y Azure, secretos de Kubernetes, configuraciones de Docker, credenciales de bases de datos, estado de Terraform y monederos de criptomonedas.
  • Sellarlo todo bajo una clave pública RSA-4096 incrustada en el código, con cifrado híbrido, y enviarlo a scan.aquasecurtiy.org.

Lee ese nombre de host otra vez. Es el propio dominio del proveedor con dos letras intercambiadas. Para un feed de reputación de dominios, o para un analista que repasa el tráfico de salida a las 2 de la madrugada, un escáner hablando con algo que se parece a aquasecurity.org es la línea menos llamativa de la página.

Si la subida fallaba, el ladrón creaba un repositorio público llamado tpcp-docs en la propia cuenta de GitHub de la víctima y adjuntaba el botín como activos de release.

SOCRadar fechó la primera recolección dieciocho minutos después de que el build malicioso saliera al aire.

Marzo de 2026 — ventanas de exposición, a escala28 feb.19 mar.22–23 mar.24 mar.14 ago.PAT robado · rotación no atómicatrivy-action — 76 de 77 tags reescritos~12 hsetup-trivy~4 hv0.69.4~3 hDocker Hub .5 / .6~10 hLiteLLM 1.82.7 / 1.82.840 min — la ventana que todos miraron2.085 de 2.188 organizaciones — 95 % — recolectadas por completo en este tramoreatribuidoVentanas y versiones según el aviso de Aqua Security GHSA-69fq-xp46-6x23. Recuentos a nivel de registro de SOCRadar,reportados por SecurityWeek, 2026-08-14. El eje horizontal es esquemático dentro de cada día.
Cada ventana de artefacto de la campaña de marzo se midió en horas, y cada una se cerró antes del aviso que la nombró. La ventana sobre la que actuó el mundo, cuarenta minutos en PyPI cinco días más tarde, fue la última en abrirse.

Lo que salió de las canalizaciones

Hudson Rock obtuvo el archivo y Help Net Security informó de su tamaño el 13 de agosto: 153 GB, 433.909 archivos, 118.829 volcados de runners de CI asociados a 2.488 dominios corporativos. El recuento independiente de CloudSEK, de unos 434.000 archivos, situó la exposición cerca de 2.500 organizaciones y más de 430.000 canalizaciones. Alon Gal, CTO de Hudson Rock, lo llamó un “esfuerzo global de divulgación ética” y dijo que la magnitud “nos empuja a un mundo completamente nuevo en cuanto al tipo de respuesta necesaria”. La lectura de Kevin Beaumont: “Es una brecha masiva de cadena de suministro por culpa de una mala seguridad de la IA.”

El desglose por organización de SOCRadar va más lejos. Más de mil organizaciones perdieron JWT y tokens de autenticación. Cientos perdieron claves privadas, claves de acceso de AWS, tokens de GitLab, claves de API de OpenAI, webhooks de Slack, tokens de GitHub Actions y claves de API de Google. Una organización perdió unos 3.477 secretos individuales. Mil cien expusieron las direcciones de correo de sus committers. Los runners eran GitHub Actions, GitLab CI, Jenkins, Bitbucket, CircleCI y Buildkite. Alemania, Brasil y Francia se llevaron la peor parte, y las recolecciones se negocian ahora en Telegram.

Cada control de esa cadena necesitaba un nombre

Alinea las defensas que existían en marzo y anota qué necesitaba cada una antes de poder actuar.

El aviso necesitaba que Aqua identificara el compromiso. La retirada necesitaba que PyPI identificara el paquete. La lista de IoC necesitaba que alguien leyera scan.aquasecurtiy.org como hostil y no como una errata de un proveedor en el que todo el mundo confía. La lista de comprobación de “¿instalaste entre las 10:39 y las 16:00 UTC?” necesitaba las cinco horas correctas. La reputación de dominio necesitaba que el dominio tuviera reputación. La reatribución que dijo a 2.085 organizaciones que habían estado mirando la semana equivocada necesitaba que apareciera un archivo filtrado y que un equipo de investigación se sentara con él, cinco meses después.

Cada uno de esos es un control de identidad: actúa sobre una cosa después de que alguien nombra la cosa. Nombrar es como el ecosistema comparte conocimiento, y funciona, así que esto no es una queja sobre quienes redactan avisos. Es una observación sobre el orden de las operaciones. Un control de identidad llega después del nombramiento, y en esta campaña cada artefacto estuvo activo entre tres y doce horas. Los dieciocho minutos entre la publicación y la primera recolección responden a la pregunta de si ese orden era suficiente.

Los controles que cambian el desenlace aquí son los que no necesitan el nombre.

Un reloj, no un oráculo

Bromure Agentic Coding pasa cada descarga de paquete por el proxy MITM del lado del host antes de que un byte llegue a la VM, y la barrera de edad es la única capa de cadena de suministro activada por defecto. Rechaza cualquier versión de paquete más joven que un umbral. Dos días, de fábrica.

No realiza ninguna consulta. No tiene opinión alguna sobre el mantenedor, el publicador, la firma o el nombre. Lee la marca de tiempo de publicación y aplica un suelo, sobre la idea de que una release fresca es la que más probablemente haya sido secuestrada hace una hora, y de que esperar a que pase esa ventana le cuesta casi nada a un desarrollador en activo.

Contra esta campaña, eso es la pelea entera. LiteLLM 1.82.7 y 1.82.8 existieron cuarenta minutos. Un suelo de dos días significa que, desde dentro de la VM, nunca existieron. El proxy retira las versiones demasiado frescas de los metadatos del registro y reorienta latest y las demás dist-tags hacia la versión superviviente más reciente, así que pip install litellm y todos los rangos semver resuelven a 1.82.6 sin error y sin nada que un agente pueda esquivar. Pide una versión envenenada con un pin exacto y el respaldo de descarga de artefactos devuelve un 451 cuyo cuerpo indica la edad real del paquete frente al mínimo exigido. Pip lo imprime literalmente:

Bromure Supply-Chain Security blocked this request: pypi package
[email protected] published 34 minutes ago — policy requires 2 days minimum

La barrera cubre npm, PyPI, Cargo, RubyGems y Packagist con datos completos de hora de publicación. Para pip realiza una consulta bajo demanda contra la API JSON de PyPI, ya que el índice PEP 503 por defecto no lleva marcas de tiempo. Cada decisión aterriza en el Registro de Seguridad (Ventana → Registro de Cadena de Suministro…) según ocurre.

CONTROL DE IDENTIDAD — actúa cuando el nombre existeartefacto publicadot + 0alguien lo notahoras … mesesatribuciónaviso, IoCel control bloqueasecretos salen: t + 18 minreatribuido: t + 5 mesesSUELO TEMPORAL — actúa en la descargaartefacto publicadot + 0el agente resuelve la dependenciael proxy lee el timestampedad < 2 días → no se sirvemetadatos reescritos, o 451no requiere nombre,no requiere aviso
Un control de identidad tiene que esperar a que el mundo nombre el artefacto. Un suelo temporal nunca aprende el nombre y nunca lo necesita: compara una marca de tiempo de publicación con un número que elegiste de antemano.

Lo que consigue un volcado de memoria

La mejor jugada del ladrón fue leer la memoria del worker del runner de CI para recuperar secretos que el enmascaramiento de logs había ocultado. Funciona porque el enmascaramiento es cosmético y el proceso guarda los valores.

Ejecútalo contra un espacio de trabajo Bromure y recupera marcadores, porque el proceso guarda marcadores. La frontera del cable es el mecanismo central del producto: cada credencial que configuras aparece dentro de la VM como una falsificación que preserva la estructura, mientras el valor real permanece cifrado en tu Mac. El proxy del host lo sustituye en el cable después de que la petición ha salido de la VM, y solo cuando la petición va dirigida al host para el que se acuñó esa credencial.

Recorre la propia lista del ladrón contra una VM Bromure:

/proc/*/environ

ANTHROPIC_API_KEY es sk-ant-api03-brm-…. OPENAI_API_KEY es sk-brm-…. GH_TOKEN es ghp_ más 36 caracteres. Preservan la estructura, así que claude y gh los aceptan sin rechistar, y no valen nada para nadie más.

/proc/<pid>/mem

Volcar el proceso recupera los mismos marcadores que guarda el entorno. Ninguna copia privilegiada se esconde más adentro en la memoria: el proxy del host hace la sustitución fuera de la VM, después de que los bytes hayan salido.

Claves privadas SSH

No hay ninguna. SSH_AUTH_SOCK apunta a un puente de ssh-agent sobre el puerto vsock 8444. Los bytes de la clave privada nunca entran en el invitado y no pueden leerse desde él. Bromure deja tu agente de sesión de macOS sin exponer, por diseño.

Nube, k8s, Docker, bases de datos

~/.aws/config, ~/.kube/config, ~/.docker/config.json, ~/.git-credentials, ~/.config/doctl/config.yaml. Todos presentes, todos poblados, todos falsos: brm-k8s-…, brm-docker-…, brm-db-…, glpat- más 20, dop_v1_ más hexadecimal.

Bromure deriva cada falsificación a partir del valor real más una sal de 32 bytes propia de la instalación, mediante HKDF-SHA256, de modo que una herramienta que toma la huella de su propia clave (Claude Code cachea un hash de clave) nunca ve rotar la credencial entre sesiones. Tampoco hay un interruptor que olvidar. El proxy es la única ruta de la VM hacia la red, así que una petición que lo esquiva lleva un marcador y falla la autenticación en el destino. La frontera falla en cerrado, por construcción.

El typosquat que nadie había visto nunca

Ahora la exfiltración. El 19 de marzo, scan.aquasecurtiy.org no tenía reputación, ni historial, ni motivo para estar en la lista de bloqueo de nadie. Por eso lo eligió TeamPCP.

El detector de compromiso de Bromure no lee nada de eso. Vigila tus credenciales en lugar de la reputación del destino. Un autómata de Aho-Corasick construido a partir de las falsificaciones acuñadas para el espacio de trabajo barre cada petición saliente, cabeceras y cuerpo. Cuando una de esas falsificaciones aparece en una petición dirigida a un host fuera del ámbito para el que se acuñó, el proxy trata la petición como intento de exfiltración:

  1. El proxy rechaza la petición con un HTTP 451 y no reenvía ni un solo byte al destino.
  2. Bromure pausa la VM en el acto.
  3. Una alerta nombra lo ocurrido: un intento saliente de filtrar una credencial de sesión a un host para el que no se acuñó.

Bromure marca entonces el espacio de trabajo como comprometido, y el siguiente arranque exige borrar la imagen de disco de la VM y el home persistente antes de iniciar. Tus tokens, claves SSH y ajustes del espacio de trabajo sobreviven a eso. El Inspector de Trazas (⇧⌘I) y bromure-cli trace leaks muestran el host infractor y la petición exacta, así que puedes saber en un minuto si la responsable fue una dependencia o una instrucción inyectada por prompt.

Una acuñación con forma AKIA limitada a amazonaws.com, apareciendo en un POST hacia scan.aquasecurtiy.org, dispara ese autómata al primer intento. El detector jamás ha oído hablar del dominio y no le hace falta. Sabe que esta credencial tiene una sola familia de destinos legítima, y que esta es otra. No hay nada que habilitar; funciona en cada petición.

La cronología que ya posees

El problema de registro aquí sobrevive al incidente.

Los investigadores reconstruyeron la cronología desde fuera, a partir de un archivo filtrado de 153 GB que tuvo que aparecer, ser obtenido y ser cribado antes de que nadie pudiera decir qué ventana importaba. Las víctimas no podían responder desde sus propios registros, porque esos registros omitían los dos hechos que lo decidían: qué artefactos descargaron sus canalizaciones y cuándo, y adónde enviaron tráfico los procesos resultantes.

Un espacio de trabajo Bromure produce ese registro como efecto secundario de su funcionamiento. Cada descarga cruza el proxy del host, así que el Registro de Seguridad guarda cada decisión de barrera de edad, de OSV, de socket.dev y de 451 como un flujo en vivo. Cada petición cruza el mismo proxy, así que bromure-cli trace ls te da host, método, estado, latencia y las marcas swap×N / LEAK×N por espacio de trabajo, trace hostnames lista cada host distinto que contactó una sesión, y trace summary lo agrega todo. Todo ello queda cifrado en reposo en tu Mac bajo la clave maestra de la bóveda.

Así, la pregunta que SOCRadar respondió en agosto, ¿me recolectaron, y cuándo?, se convierte en una consulta de dos minutos que ejecutas tú mismo en marzo, contra tus propios datos, sin esperar a que nadie publique el nombre correcto.

Ese es el argumento. Nadie puede hacer que el ecosistema nombre las cosas más rápido: marzo mostró la respuesta a incidentes del propio proveedor dejando tras de sí una cola de diecinueve días y una atribución equivocada. Lo que sí puedes cambiar es si tu desenlace depende del nombre. Pon un reloj delante del registro, pon marcadores en la máquina que ejecuta el código, y lleva tu propio registro de qué descargó y con quién habló.


Fuentes: SecurityWeek, “Trivy, Not LiteLLM Behind the 2,500 Org Compromise” (14 de agosto de 2026) · Aviso de Aqua Security GHSA-69fq-xp46-6x23 · StepSecurity, “Trivy Compromised a Second Time” · The Hacker News (12 de agosto de 2026) · Help Net Security (13 de agosto de 2026) · CrowdStrike, “From Scanner to Stealer”