El commit era una rama
Air divulgó Plugin4Shell el 17 de septiembre: los marketplaces de plugins de Claude Code, Codex, Gemini CLI y Copilot fijan cada plugin a un hash de commit de cuarenta caracteres, y los agentes hacen checkout de ese hash sin confirmar nunca que es lo que recibieron. Un atacante que nombra una rama igual que el hash fijado gana el checkout, y el actualizador en segundo plano hace el resto, sin aviso y sin clic. Anthropic y OpenAI parchearon. Google no lo hará, y Microsoft no lo ha hecho. En un workspace de Bromure Agentic Coding, el código que gana el checkout aterriza en una VM que solo guarda credenciales señuelo, detrás de una política de push y de un conjunto de reglas de egreso aplicados en el host.
Todas las guías de cadena de suministro dicen que fijes las dependencias a un hash, porque un número de versión es una etiqueta que alguien puede mover y un hash es el contenido mismo. Cuatro agentes de código siguieron ese consejo. Ninguno comprobó que el hash fuera lo que recibió.
Un plugin pasa la revisión. Lo lees tú, o lo lee tu equipo de plataforma, y entra en la lista aprobada fijado a un commit: cuarenta caracteres hexadecimales que solo pueden describir un árbol, porque cambiar el árbol cambia los caracteres. Ese es el atractivo. Cualquiera con el acceso adecuado puede mover una etiqueta o republicar una versión, pero se supone que un hash de commit es un hecho.
El 17 de septiembre, tres investigadores de Air (Or Nevo, Dor Granat y Niv Hoffman) publicaron Plugin4Shell. Su hallazgo: la fijación vale solo lo que valga aquello que la resuelve, y el resolvedor de cuatro grandes agentes de código no comprueba su propio trabajo. The Register lo cubrió el mismo día. Claude Code, el Codex de OpenAI, el Gemini CLI de Google y el Copilot de Microsoft están todos afectados.
Git prefiere la ref
Un hash de commit es contenido, y también es un nombre. Las ramas y las etiquetas también lo son, y git las resuelve todas con la misma búsqueda. Dale a git una cadena que sea a la vez nombre de rama e id de objeto y tendrá que elegir; elige la rama. Los investigadores lo resumen en una línea: “cuando un nombre es a la vez una ref válida y un id de objeto, git prefiere la ref”.
Git sí avisa. Imprime una nota de ambigüedad para esa referencia, en una terminal que no estás leyendo, porque quien ejecuta el checkout es un actualizador en segundo plano.
Los nombres de rama de git admiten cuarenta caracteres hexadecimales. Un atacante que controla el repositorio del plugin crea una rama con el mismo nombre que fijó el marketplace y la convierte en la rama por defecto. El clon se trae esa rama junto al commit del mismo nombre. Entonces el agente ejecuta su checkout:
git clone <plugin repo> ./
git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
El checkout aterriza en la rama. Gemini CLI llega al mismo sitio por otro camino:
obtiene el commit correcto con --ref y luego ejecuta
git checkout FETCH_HEAD, que resuelve a una rama llamada FETCH_HEAD si el
repositorio tiene una.
Cinco pasos, y el último no tiene clic
Los investigadores exponen el ataque en cinco movimientos, y los tres primeros son cosas que un marketplace bien llevado hace a propósito.
Plantar. Publicar un plugin benigno. Pasa la revisión en el commit aaa…aaa
porque no tiene nada malo.
Adopción. Los desarrolladores lo instalan, fijado al commit revisado. Este es el paso en el que haces lo que las guías te dicen que hagas.
Cambio de versión. El marketplace refija a un commit más nuevo, bbb…bbb,
también benigno y también revisado.
Rug-pull. El atacante crea una rama llamada bbb…bbb, la pone por defecto y
la apunta a código malicioso. No republica nada y el registro del marketplace no
se mueve. Los cuarenta caracteres que irías a comprobar son los cuarenta
caracteres que ya comprobaste.
De la actualización automática al RCE. Claude Code y Codex actualizan los plugins instalados en segundo plano por defecto. La siguiente actualización programada hace checkout de la rama. Air describe ese paso como “sin aviso, sin clic”.
Hay una segunda puerta de entrada que se salta la plantación: tomar el repositorio de un autor de plugin legítimo y empujar la versión maliciosa a todos los agentes que ya tienen el plugin instalado. El truco de la rama es el mismo, y no hay revisión que pasar.
Air describe el resultado como “ejecución remota de código completa en la máquina del empleado”, corriendo con los permisos de ese empleado. El atacante no necesita escalar privilegios. El código corre como la persona dueña del portátil, y alcanza sus archivos y su acceso a producción.
Es la tercera vez que estos investigadores cuentan la misma forma. Su trabajo anterior sobre skills de agente maliciosas describe una que alcanzó a más de 26.000 agentes, y una campaña de secuestro que se hizo con 925 skills ya en uso en 134.000 agentes. Aquellas campañas necesitaban que instalaras algo malo; esta necesitaba que instalaras algo bueno y esperaras.
Dos de los cuatro no tienen parche
Air informó a los cuatro fabricantes en junio.
Anthropic parcheó Claude Code el 17 de junio, en la versión 2.1.179. La corrección de OpenAI llegó en Codex 0.146.0 y se verificó el 12 de agosto. Google respondió el 4 de agosto que Gemini CLI está descontinuado y no se parcheará. Google remite a los usuarios a Antigravity, así que una instalación de Gemini CLI que siga en disco sigue siendo vulnerable. Microsoft no entregó nada para Copilot. GitHub dice que su propia plataforma bloquea nombres de rama y de etiqueta que parezcan SHA de commit, lo que cubre los plugins alojados en GitHub y no hace nada por un repositorio de plugin alojado en otro sitio, Bitbucket incluido. The Register señala, usando la cifra de la propia Microsoft, que casi el 90 % de las empresas del Fortune 500 usan Copilot.
La corrección en sí es una línea, y tiene que ejecutarse dentro del agente:
test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort
Resolver el commit que aterrizó en el árbol de trabajo, y negarse a continuar si no coincide. Ningún marketplace puede hacer esto por ti, porque es tu agente quien resuelve la fijación en tu máquina, después de que el marketplace haya terminado de hablar.
Para un buen número de desarrolladores que leen esto hoy, “actualiza tu agente” no es una respuesta disponible. Queda la otra pregunta: ¿qué encuentra el código cuando gana el checkout y empieza a ejecutarse?
Dentro de un workspace de Bromure Agentic Coding
La frase de Air, corriendo con los permisos del empleado, es exacta. Un workspace existe para hacerla falsa.
En Bromure Agentic Coding, el agente corre dentro de una VM
Linux virtualizada por hardware en tu Mac, y la VM nunca guarda una credencial
real. Al arrancar la sesión, la app escribe señuelos que preservan la estructura
en el entorno donde corre el agente: ANTHROPIC_API_KEY, GH_TOKEN,
LINEAR_API_KEY y el resto como variables de entorno, y falsificaciones
equivalentes en ~/.git-credentials, ~/.docker/config.json, ~/.kube/config,
~/.aws/config y los archivos de configuración MCP del agente. Cada señuelo
mantiene la forma que sus herramientas esperan, así que las herramientas lo
aceptan: un señuelo de GitHub es ghp_ más 36 caracteres, uno de Anthropic
empieza por sk-ant-api03-brm-, un token bearer de Kubernetes empieza por
brm-k8s-. Bromure deriva cada uno del valor real mediante HKDF-SHA256 con una
sal por instalación, lo que lo mantiene estable entre sesiones y sin valor para
quien lo recoja.
Los valores reales están cifrados en el host. Un proxy del lado del host los sustituye en el cable después de que la petición haya salido de la VM, y solo cuando el destino coincide con el host para el que se acuñó esa credencial. Un plugin que barre el directorio personal en busca de archivos de configuración, que es lo primero que hace esta clase de carga útil, recoge un conjunto de señuelos bien formados.
El push tiene que aprobarse en el host
Lo más valioso que un plugin comprometido puede hacer en la máquina de un desarrollador es empujar. Así es como un portátil se convierte en cien repositorios.
Guardrails clasifica cada llamada que el agente hace a GitHub. Un git push por
HTTPS llega al proxy como git-receive-pack, que cuenta como escritura. El modo
solo lectura lo rechaza. El modo preguntar antes de escribir levanta un
diálogo en tu Mac, titulado
Allow write on "<scope>" from workspace "<name>"?, que muestra la operación tal
cual en lugar de un resumen. Un git fetch cuenta como lectura y pasa sin
interrumpirte.
La clasificación corre en el proxy del host, fuera de la VM. El código dentro del invitado no puede apagarla ni rodearla, porque no corre en la máquina que toma la decisión.
La llamada a casa se topa con una regla que el invitado no puede editar
Cada workspace tiene un cortafuegos de egreso: una lista ordenada, al estilo pf,
de reglas de permiso y denegación sobre host, rango de IP, protocolo, puerto y,
para el tráfico web, el verbo HTTP individual.
allow web api.example.com GET,POST le da al agente consultas sin mutaciones, en
el cable, sin importar qué herramienta dentro de la VM haga la petición.
Dos capas lo aplican. El conmutador de red virtual aplica las reglas a cada flujo por IP de destino y por nombre de host espiado en el DNS, cubriendo TCP y UDP simples además de HTTPS. El proxy las aplica de nuevo por nombre de servidor TLS y método HTTP. Bromure desvía el tráfico de los puertos 80 y 443 de la VM hacia el proxy sin la cooperación del invitado, así que nada de dentro puede librarse de la inspección. Tus ediciones alcanzan las sesiones en curso al instante, lo que importa el día en que lees una divulgación como esta.
Debajo de eso está el detector de compromiso, activado por defecto y sin nada que configurar. El proxy examina cada petición saliente en una sola pasada buscando los tokens señuelo acuñados para ese workspace. Un señuelo dirigido a un host para el que nunca se acuñó es la firma de una exfiltración, y el proxy le responde con un HTTP 451 sin reenviar ni un byte. Pausa la VM en el acto y lanza una alerta que ofrece apagar el workspace, exportar el disco y la carpeta personal para investigar, o continuar por tu cuenta y riesgo. Bromure marca el workspace como comprometido, y el siguiente arranque borra el disco de la VM y la carpeta personal conservando tus tokens, claves SSH y ajustes del workspace. Solo se fue el señuelo, así que no hay nada que rotar.
Cero clics significa que no hay nada que notar
La propiedad más afilada del paso cinco es su silencio. Una actualización de plugin en segundo plano no produce ni aviso ni llamada a herramienta, así que la propia interfaz del agente no le da al desarrollador nada que pillar. El punto de Air es que el exploit corre en la parte del sistema que no tienes motivo para vigilar.
Un workspace mueve la vigilancia a otro sitio. Los motores de seguridad escriben
cada decisión como una fila en la ventana Security Timeline de tu Mac: veredictos
del cortafuegos (evil.example:443 tcp — blocked), aplicaciones de Guardrails
(DELETE api.github.com/repos/… — blocked), credential brokering incluida la
fila roja blocked — exfiltration attempt, VM paused, además de veredictos de
cadena de suministro y aciertos de inyección de prompt, con colores y
filtrables. Una sesión también puede grabar una traza cifrada de los comandos que
se ejecutaron y de los hosts que alcanzó.
Un plugin que se actualizó solo sin decírtelo todavía tiene que hacer peticiones, y cada una cruza una frontera que lleva sus propias notas al otro lado del hipervisor.
Comprueba primero la versión de tu agente
Claude Code 2.1.179 y Codex 0.146.0 contienen la corrección. Si usas Gemini CLI, no la habrá. Si usas Copilot, todavía no la hay.
Desactiva la autoactualización de plugins donde puedas
La propiedad de cero clics viene del actualizador en segundo plano. Las actualizaciones manuales devuelven a un humano al bucle en el paso cinco.
Verifica tú mismo el checkout en la CI
git rev-parse HEAD después de un checkout fijado, comparado con la
fijación, es la misma comprobación de una línea que los fabricantes tuvieron
que añadir. Funciona en cualquier sitio donde clones por SHA.
Da por hecho que alguien gana el checkout
La fijación, la revisión y la curación del marketplace aguantaron todas aquí y el ataque entró igualmente. El desenlace descansa entonces en lo que el código ganador puede alcanzar.
Plugin4Shell atrapa al desarrollador cuidadoso en los mismos términos que al descuidado. Quien instaló lo que estaba de moda no salió peor parado que quien leyó el código fuente, fijó el hash y anotó la fijación. El direccionamiento por contenido debía proteger a ese segundo desarrollador, y no lo hizo, porque git resuelve la dirección mediante una búsqueda que también usa esos caracteres para otra cosa.
Sigue fijando. Y luego da por hecho que un control que se apoya en una búsqueda o en una coincidencia de nombres recibirá algún día la respuesta equivocada mientras duermes. Diseña para esa mañana: ejecuta el agente en algún sitio que no guarde ninguna clave que merezca la pena llevarse, y mantén el registro de lo que hizo al otro lado de una frontera que el código no puede alcanzar.