Volver a todas las publicaciones
Publicado el · por Renaud Deraison

Una sesión, cien repositorios

El informe AI Risk and Resilience 2026 de Mandiant describe a un atacante que tomó el control de una sesión activa de asistente de código en un proveedor de SaaS, logró que por ella se instalara un paquete de PyPI envenenado, robó tokens OAuth de GitHub y propagó el gusano Shai-Hulud por unos 100 repositorios internos. El informe no dice cómo se tomó la sesión. En un workspace de Bromure Agentic Coding no hace falta que lo diga: el token de esa sesión es un señuelo `ghp_`, el intento de sacarlo pausa la VM, y los push que convierten una máquina en cien repositorios se detienen en el proxy del anfitrión.

Alguien tomó el control de una sesión de código que estaba corriendo, autenticada y de confianza, en la propia máquina de un desarrollador. Todo lo que vino después, hasta cien repositorios, salió de lo que esa máquina tenía guardado en ese momento.

Un desarrollador de una empresa de software como servicio le preguntó a su asistente de código qué paquete usar para un problema corriente. El asistente nombró uno. El desarrollador dijo que sí, que es justo para lo que sirve tener un asistente, y pip install hizo el resto.

Para entonces un atacante ya tenía la sesión, y fue él quien escribió esa recomendación.

Mandiant describe el caso en su AI Risk and Resilience Report 2026, publicado este mes por la rama de respuesta a incidentes de Google Cloud; The Hacker News lo contó el 16 de septiembre. El atacante secuestró una sesión activa de asistente de código en el puesto de trabajo de un desarrollador. Cinco pasos más lo llevaron hasta unos cien repositorios de código internos y al espacio de nombres de paquetes de la propia empresa, donde un segundo empleado se descargó la versión envenenada.

La cadena, salto a salto

Un puesto de trabajo, seis pasoslo que gasta el paso1un atacante toma una sesión activa de asistentemétodo no revelado en el caso de estudio públicola sesión2el asistente recomienda software envenenadoel desarrollador lo acepta, tal como está diseñadoel sí del desarrollador3un paquete de PyPI comprometido instala un ladrón de datostráfico de dependencias corriente, instalación corrienteconfianza del registro4se leen los tokens OAuth de GitHub del puestoel pivote: todo lo que sigue depende de estotokens guardados5Shai-Hulud se propaga a unos 100 repositorios internosse llevan secretos de repositorio y código fuenteacceso de escritura6se envenena un paquete del espacio de nombres de la empresaun segundo empleado se descarga esa versión y se infectapermisos de publicación
Los seis pasos que describe Mandiant, y la credencial que gasta cada uno. Mandiant no revela método alguno para el paso 1. En cada paso posterior, el atacante usa una autorización que la máquina del desarrollador ya tenía guardada: la confianza de la propia sesión, luego la del gestor de paquetes, luego la de GitHub, luego los permisos de publicación de la empresa.

Mandiant se guarda justo la parte que más querría saber. El caso de estudio público no dice cuándo ocurrió la intrusión, y no dice cómo consiguió el atacante el control de una sesión en curso. Cookies de navegador robadas, una extensión maliciosa, una CLI comprometida en una máquina compartida: el informe no elige ninguna. Quien lo lea buscando el parche se irá sin él.

Los pasos 2 a 6 no contienen ningún exploit. Una instalación de paquete funciona como funcionan las instalaciones de paquetes, y el código que corre como el desarrollador lee los tokens que están en el disco del desarrollador. Un git push que lleva un token válido empuja, y una publicación que lleva permisos de publicación válidos publica. Un paso fue una intrusión; los otros cinco fueron gasto.

El multiplicador son las credenciales, y no para de crecer

Shai-Hulud es el gusano autorreplicante de la cadena de suministro que lleva comiéndose cuentas de mantenedores desde 2025, y aparece como carga útil en una historia como esta porque el paso 5 es exactamente para lo que sus autores lo construyeron. GitGuardian desmontó una variante reciente en agosto y la encontró barriendo 469 ubicaciones de credenciales, frente a 189 en compilaciones anteriores: entornos de desarrollo, herramientas de CI/CD, configuración de la nube, configuraciones de herramientas de IA, archivos de configuración de gestores de paquetes, historial del shell, archivos .env, ajustes del IDE, cachés de CLI. El gusano lee un archivador en lugar de salir a cazar un fallo.

El análisis de GitGuardian resume la categoría en una frase:

“Los atacantes han dejado de intentar romper las relaciones de confianza y han empezado a usar las credenciales que ya hacen que esas relaciones funcionen.”

Un puesto de trabajo se convirtió en cien repositorios porque tenía guardada autorización para cien repositorios. Ese número salió de un inventario, y usted decide lo que entra en el inventario.

La sesión solo tenía señuelos

Bromure Agentic Coding ejecuta el agente dentro de una máquina virtual Linux en su Mac, y esa máquina no guarda ningún secreto real. No hay que activar nada para conseguirlo. Es la forma en que un workspace guarda una credencial, sin más.

Su token real de GitHub se queda cifrado en el anfitrión. La VM recibe un falso que conserva la estructura: ghp_ seguido de 36 caracteres, 40 en total, de modo que las comprobaciones de prefijo y longitud del propio gh pasan sin queja. Bromure lo exporta como GH_TOKEN y lo escribe en ~/.git-credentials y en la configuración de gh, y es la única credencial de GitHub que hay en esa máquina. Un proxy en el anfitrión sustituye el valor real en el cable después de que la petición haya salido de la VM, y solo cuando el destino coincide con el anfitrión para el que se acuñó la credencial. Cada falso es determinista, derivado del valor real más una sal de 32 bytes propia de la instalación mediante HKDF-SHA256, así que un cliente que tome la huella de su propia clave no ve rotación aparente entre sesiones.

Ejecute el paso 4 contra eso. El ladrón de información llega, barre las 469 ubicaciones, encuentra la variable de entorno, encuentra ~/.git-credentials, encuentra la configuración de gh y envía lo que encontró. Cada lectura tiene éxito, y cada archivo está donde el gusano lo esperaba. El atacante acaba con una cadena de cuarenta caracteres que no autentica en ninguna parte, salvo volviendo a pasar por un Mac muy concreto.

Los pasos 5 y 6 necesitan los dos que el paso 4 haya producido un token que funcione, y el paso 4 produjo un señuelo.

El robo es también la alarma

Un token falso tiene un único destino legítimo. Ninguna petición inocente lleva un señuelo ghp_ acuñado para github.com a otro anfitrión, así que el proxy examina cada petición saliente, cabeceras y cuerpo, en busca de cualquier falso que salga de su propio ámbito. Ejecuta un autómata de Aho-Corasick, lo bastante barato como para apuntarlo a todo el tráfico.

Cuando el proxy encuentra uno, hace algo más que registrarlo:

  • rechaza la petición con HTTP 451, y ni un byte de ella llega al destino;
  • Bromure pausa la VM en el acto;
  • una alerta le ofrece Apagar, Guardar para investigación (exportar antes el disco, la carpeta personal y las carpetas compartidas para el análisis forense) o Continuar por su cuenta y riesgo;
  • la Security Timeline gana una fila roja de Credential brokering;
  • Bromure marca el workspace como comprometido, de modo que el siguiente arranque borra la imagen de disco y la carpeta personal persistente. Sus tokens, claves SSH y ajustes del workspace sobreviven a ese borrado.

Usted no activa nada de esto; el detector está siempre encendido. El intento de exfiltración del paso 4 es lo que detiene la sesión, así que el incidente termina con una VM pausada y una entrada en la línea de tiempo, en lugar de cien repositorios y un caso de estudio.

Cien repositorios son cien push

Supongamos que quiere el cinturón además de los tirantes. El paso 5 es un gusano escribiendo en repositorios, y el anfitrión clasifica las escrituras.

Las protecciones de un workspace llevan una política de escritura por servicio. Para GitHub, esa política lee git sobre HTTPS igual que la API REST: un git push llega como git-receive-pack y cuenta como escritura, mientras que un git fetch llega como git-upload-pack y cuenta como lectura. Solo lectura rechaza los push. Preguntar antes de escribir, el valor por defecto de los workspaces nuevos, retiene cada uno para un diálogo del lado del anfitrión que nombra la operación. Los fetch pasan con ambas, así que el agente sigue trabajando.

El proxy toma esa decisión en el anfitrión, fuera de la VM. Un agente cuya sesión pertenece a otra persona no puede desactivar la política ni rodearla, porque la política no se ejecuta en el lado del agente de la línea. Un gusano que quiere cien repositorios recoge cien rechazos, o le pregunta cien veces.

Y el paquete todavía tenía que llegar

El paso 3 es donde Mandiant da consejos concretos: validar las dependencias de terceros recomendadas por la IA contra sumas de verificación criptográficas y listas de permitidos, y encaminar el tráfico de dependencias por repositorios que usted controle.

En un workspace, cada descarga de paquete pasa ya por el anfitrión, puesto que el proxy es la única ruta de la VM hacia la red. Bromure intercepta PyPI en pypi.org y files.pythonhosted.org y aplica allí la política del anfitrión. El pip.conf de dentro de la VM puede apretar lo que el proxy sirvió; no puede aflojarlo. Tres capas hablan por los paquetes de Python:

  • la barrera de edad, activada por defecto con un mínimo de dos días, que rechaza las versiones más jóvenes que el corte, partiendo de que una publicación recién salida es la que tiene más probabilidades de haber sido secuestrada hace nada. Los índices PEP 503 no llevan marcas de tiempo, así que Bromure consulta la hora de publicación bajo demanda;
  • la comprobación OSV contra api.osv.dev, gratuita y sin clave, que bloquea cualquier versión que lleve un aviso de seguridad con la gravedad que usted elija o superior;
  • el filtro de paquetes comprometidos de socket.dev, que salta con malware, malware conocido, typosquatting y scripts de instalación maliciosos.

Un bloqueo vuelve como un HTTP 451 cuyo cuerpo empieza por Bromure Supply-Chain Security blocked this request: seguido del motivo, y pip lo imprime tal cual. Usted ve por qué falló la instalación, y el agente también, que muchas veces puede fijar por su cuenta una versión más antigua. Esa versión más antigua es justo hacia la que la barrera de edad lo estaba dirigiendo.

Un puesto de trabajo corrientepaso 3: el paquete se instalael registro sirve la versión envenenadanada en el camino juzga la procedenciapaso 4: se leen los tokenstokens OAuth reales: entorno, config, historialla exfiltración se lleva una credencial válidapaso 5: el gusano empujaun token válido empuja, así que empujaunos 100 repositorios, y el espacio de nombresDentro de un workspace Bromurepaso 3: la descarga se juzga antesedad, OSV, socket.dev, en el anfitrión451 antes de que un byte llegue a la VMpaso 4: la misma lectura funcionalas 469 ubicaciones tienen señuelos ghp_enviar uno: 451, y la VM se pausapaso 5: el push se clasificagit-receive-pack rechazado o consultadogit fetch sigue pasando; el trabajo continúa
Los mismos tres pasos que sostienen el ataque, en dos máquinas. A la izquierda, un puesto de trabajo corriente: tokens reales en el disco, un robo que funciona y push que pasan. A la derecha, un workspace de Bromure Agentic Coding: el anfitrión rechaza la versión envenenada antes de que llegue a la VM, el mismo robo devuelve un señuelo y pausa la máquina, y el anfitrión clasifica los push, donde una sesión secuestrada no tiene voto.

Los tres controles de Mandiant, y dónde viven

El informe cierra el caso de estudio con tres recomendaciones. Cada una nombra un sitio donde un workspace de Bromure ya pone la frontera.

Validar las dependencias recomendadas por la IA

Mandiant pide sumas de verificación y listas de permitidos para todo lo que sugiera el asistente. Su workspace aplica su política a la respuesta del registro antes de que la VM la vea. El proxy quita del listado de versiones las demasiado recientes, de modo que desde el punto de vista del agente todavía no existen, y rechaza una versión demasiado reciente fijada a mano citando en el error la edad real del paquete. Las claves de los servicios de reputación se quedan en el anfitrión, fuera de la máquina que ejecuta la instalación.

Mantener lejos las claves en bruto y los tokens de larga vida

Esa es la frontera del cable escrita como consejo. Ningún archivo, variable de entorno o proceso dentro de la VM guarda una clave de API, un token OAuth, un secreto de AWS o una clave privada SSH reales. Bromure mantiene los tokens OAuth de la suscripción cifrados en el anfitrión y los renueva allí unos cinco minutos antes de que caduquen, de modo que el invitado no lleva ningún token de refresco. El canal entre ambos corre en un solo sentido: el anfitrión escribe falsos hacia dentro, y la VM no tiene ninguna llamada con la que pedir de vuelta un token real.

Encaminar el tráfico de dependencias por algo que usted controle

El proxy es la única ruta de la VM hacia fuera. Cada petición HTTPS viaja por un socket virtual hasta el anfitrión, que termina el TLS, inspecciona la petición y la reemite por la propia pila TLS de macOS. Ese único camino es lo que hace que los otros dos controles sean exigibles en vez de meros consejos. Ninguna descarga de paquete puede saltarse las comprobaciones, y ninguna credencial puede salir sin sustituir.

Y reducir el radio de explosión a propósito

Cien repositorios cayeron porque una máquina llevaba autorización para cien repositorios. Corte los workspaces uno por proyecto, o uno por frontera de credenciales. Cada uno corresponde a una sola VM, no hay dos que compartan una, y cada uno lleva las credenciales que el trabajo de ese workspace necesita. El inventario de una máquina cualquiera se fija en un panel de ajustes.

La parte que no se puede parchear

Mandiant enmarca la categoría en una frase que describe la forma más que el incidente: “una fuente de datos envenenada, una dependencia de modelo o un gancho de extensión pueden convertir a un agente de confianza en un canal para el reconocimiento interno, el movimiento lateral o la fuga autónoma de un entorno aislado.”

Tres puntos de entrada distintos, una sola frase, y vale para los tres. El punto de entrada determina cómo llega un atacante; el contenido de la máquina de un desarrollador determina hasta dónde llega. Este atacante entró por una sesión secuestrada. El siguiente entrará por otra cosa, y el informe no nombra ninguna puerta que usted pueda cerrar.

Construya para el paso posterior a la entrada. Dé por hecho que la sesión ya pertenece a otra persona, que es justo lo que el caso de estudio le obliga a suponer al no explicar nunca el paso 1, y pregúntese luego hasta dónde llega esa sesión.

Una versión de este incidente termina en el paso 3, con un 451 en el terminal y una línea en el Security Log. Otra termina en el paso 4, con una VM pausada y una fila roja en la línea de tiempo que deja constancia de que una credencial intentó salir. Ninguna de las dos llega al paso 5, y en el paso 5 estaban los cien repositorios.

Ponga el agente en una máquina que solo guarde señuelos. Instale Bromure Agentic Coding, y deje que alguien tome la sesión.