Volver a todas las publicaciones
Publicado el · por Renaud Deraison

git status ejecutó su código

El 2 de septiembre de 2026, Manifold Security divulgó GitSpawn: ocho fallos en siete agentes de codificación de línea de comandos donde el propio .git/config de un repositorio ejecuta un comando en la máquina del desarrollador. El agente lanza git status para saber en qué rama está, git actualiza su índice, y el comando elegido por el repositorio se ejecuta con los privilegios del usuario que ha iniciado sesión, fuera del sandbox, antes del aviso de confianza y, en una de las herramientas, antes incluso del login. Sin prompt, sin llamada a herramienta, sin turno del modelo. Bromure Agentic Coding no parchea ese camino. Cambia la máquina a la que ese camino lleva.

No escribiste nada y no aprobaste nada. El agente no había terminado de arrancar y, en una de estas herramientas, ni siquiera habías iniciado sesión. Una carpeta que alguien te envió ejecutó un comando en tu nombre, y fue git quien lo ejecutó.

Un colaborador externo termina un trabajo y te envía un zip del directorio del proyecto en una carpeta compartida. Lo descomprimes, entras con cd y arrancas tu agente de codificación para orientarte. El agente empieza preguntándole a git qué rama es esta y qué está modificado. Ese es todo el exploit.

El 2 de septiembre de 2026, The Hacker News informó de una divulgación de Manifold Security que cubre ocho fallos en siete agentes de codificación de línea de comandos, publicados bajo el nombre GitSpawn. Entre todas, las herramientas afectadas acumulan cerca de medio millón de estrellas en GitHub, y solo Claude Code ha superado los 77 millones de descargas npm al mes. Cuatro de los ocho seguían ejecutando comandos suministrados por el repositorio cuando Manifold volvió a probarlos el 1 de septiembre.

Un ajuste de rendimiento cuyo valor es un comando

core.fsmonitor es una opción de git pensada para quien tiene repositorios enormes. En lugar de recorrer todo el árbol para averiguar qué ha cambiado, git puede llamar a un programa auxiliar externo y preguntárselo. El valor del ajuste es el comando. Git lo lee del .git/config del propio repositorio y lo ejecuta cada vez que actualiza su índice, es decir, en git status, en git diff, en la fontanería en la que se apoya cada herramienta.

Casi todos los agentes que examinó Manifold reúnen el contexto del proyecto lanzando esos comandos al arrancar, y ninguno de ellos miraba antes el .git/config. Así que el repositorio nombra el comando y git lo ejecuta. En palabras de los investigadores, se ejecuta “con todos los privilegios del usuario que ha iniciado sesión, fuera del sandbox”. OpenAI, en su propio aviso para la variante de Codex, dijo que el auxiliar “se ejecuta fuera del sandbox de comandos de Codex y sin un aviso de aprobación del usuario”.

Un git clone no arrastra nada de esto. El clon transfiere objetos y refs, y la configuración local del repositorio de origen se queda en casa. Así que el proyecto envenenado tiene que llegarte como archivos, con su directorio .git intacto: un archivo zip, un disco compartido, una carpeta sincronizada, una memoria USB. La lista de Manifold describe cómo consultores, clientes y colegas se pasan trabajo, y es la única vía de entrega en la que no hay ninguna pull request. La propia defensa de git contra repositorios hostiles, safe.directory, comprueba si el repositorio pertenece a otra persona. Tú lo descomprimiste, así que es tuyo, y la defensa no tiene nada que decir.

El momento de la ejecución deja la protección propia de cada agente un paso demasiado tarde. En Claude Code y Hermes Agent la carga se dispara antes del aviso de confianza del espacio de trabajo, el diálogo que existe justamente para preguntar si confías en este directorio. En Qwen Code se dispara antes de que el usuario se haya autenticado, y en Grok Build se dispara con la primera tecla.

GitSpawn — de la entrega al shellla carpeta llegazip, disco compartido,carpeta sincronizada, USB —.git/config intactoel agente arrancareúne el contexto:git status · git diffgit actualiza su índicelee la config del repocore.fsmonitor = …<su comando>tu usuario, tu entorno,tu directorio homeLo que aún no había ocurridoel aviso de confianzaClaude Code,Hermes Agentiniciar sesiónQwen Code dispara antesde la autenticaciónescribir cualquier cosaGrok Build dispara conla primera teclael modelosin llamada, sin turno,nada que registrarCada barrera de la fila de arriba es un paso del arranque del agente. El comando se ejecutó antes de llegar a ellas.
La cadena entera, y las cuatro cosas que aún no habían ocurrido cuando terminó. El .git/config de un repositorio nombra un comando en core.fsmonitor; el agente lanza git status para averiguar su rama; git actualiza el índice y llama al auxiliar. Se ejecuta como el usuario que ha iniciado sesión, antes del aviso de confianza en Claude Code y Hermes Agent, antes de la autenticación en Qwen Code y con la primera tecla en Grok Build, sin ninguna llamada a herramienta y sin ningún turno del modelo en ningún punto de la secuencia.

Ocho hallazgos, siete agentes, cuatro todavía abiertos

La mitad está corregida. goose 1.44.0 lleva la corrección de CVE-2026-72718, valorada con CVSS 7.0. OpenAI publicó Codex CLI 0.131.0 y las builds de escritorio 26.519.x para CVE-2026-19592. Cursor parcheó su variante. Manifold confirmó el camino core.fsmonitor de Claude Code en 2.1.193 y Anthropic lo cerró en 2.1.196, sin publicar un aviso: Manifold señala que ninguno de los dos hallazgos sobre Claude Code aparece en los registros de avisos de npm.

La otra mitad no se ha movido. Hermes Agent tiene un CVE (CVE-2026-71963) y un aviso privado todavía sin triar después de seis intentos de contacto. Los mantenedores de Qwen Code aceptaron el informe el 7 de julio y 0.22.3 sigue ejecutando el comando. Grok Build lo sigue ejecutando. Y Claude Code tiene un segundo camino, a través de su comando ultrareview, que abusa de una clave de configuración de git distinta y seguía vivo en 2.1.252. Manifold se guardó el nombre de esa clave en lugar de repartir una plantilla de ataque reutilizable.

Anthropic parcheó el primer camino en tres días. La clase de fallo lo rodeó por otra clave, en el mismo archivo, en el mismo producto.

Esta es la superficie de ejecución de git, heredada por toda herramienta que delega en git, y git tiene más de un ajuste cuyo valor es un comando que ejecutará por ti. Los paginadores, los editores, los comandos SSH, las rutas de hooks, los drivers de diff y de filtro, y los alias que empiezan con un escape de shell aceptan todos valores de comando. Ahora cada fabricante tiene que averiguar, clave por clave, cuáles de ellas puede alcanzar un repositorio desde su ruta de arranque, frente a un esquema de configuración que gana claves con el tiempo. La corrección recomendada por Manifold, sanear la configuración de git durante las operaciones de contexto en segundo plano y desactivar core.fsmonitor, es una lista de bloqueo mantenida frente a un espacio de nombres. Los investigadores encontraron el patrón en más agentes de los que nombra el informe.

Los controles que habrían atrapado esto están aguas abajo

Alinea lo que un equipo cuidadoso tiene hoy sobre un agente de codificación: avisos de permiso en el uso de herramientas, confianza de espacio de trabajo, hooks que inspeccionan un comando antes de que se ejecute, los endpoints de transcripción que envían una sesión a un backend de cumplimiento, y OpenTelemetry para decisiones de permiso y saltos.

Todos ellos se anclan al bucle propio del agente, es decir, a una llamada a herramienta que el agente decidió hacer o a un turno que el modelo tomó. GitSpawn no produce ninguno de los dos. El proceso que ejecuta la carga es git, invocado por el agente para su propia limpieza, y el comando viene de un archivo que el agente nunca leyó. No hay llamada a herramienta que aprobar ni hook que disparar. Un equipo que ve aterrizar transcripciones de agente en su SIEM ve una sesión que arranca con normalidad y no muestra nada, el mismo punto ciego del que escribimos cuando Anthropic lanzó su Compliance API: un registro anclado a lo que llegó al modelo no puede ver lo que se ejecutó antes de que se consultara al modelo.

El comando se va a ejecutar. La pregunta que vale la pena responder es qué encuentra.

Descomprimida en tu directorio homegit status → auxiliar core.fsmonitorelegido por la carpeta, ejecutado como túlo que está a un directorio de distancia~/.ssh/id_ed25519ANTHROPIC_API_KEY, AWS_SECRET_ACCESS_KEY~/.git-credentials, ~/.aws, ~/.kube/config~/.zshrc — persistencia al próximo iniciosalida: una conexión normal de unaherramienta de dev normal, en puerto normalel log del propio agente no tiene ni una filaLa carpeta no era de confianza. La cuenta donde cayó, sí.Abierta en un espacio de trabajo Bromuregit status → auxiliar core.fsmonitorsigue ejecutándose — como ubuntu, en el invitadoVM Linux desechable · arrancada antes que la carpetaningún byte de clave privada — el ssh-agent del host firmalas credenciales de env y config son señuelos (brm_…):80 y :443 desviados al proxy del host, no desactivablesfirewall de salida: denegar por defecto → sin segunda etapaun señuelo fuera de su ámbito → 451, VM pausada,espacio marcado, hace falta borrarlo para arrancarcada petición recibe una fila, sea cual sea el procesoEl comando sigue ejecutándose. Solo que en un sitio hecho para eso.
La misma carpeta, el mismo git status, dos máquinas distintas. En tu portátil el auxiliar se ejecuta como tú, a un directorio de tus claves SSH y tus credenciales de nube, y su conexión saliente es una conexión corriente de una herramienta de desarrollo corriente. En un espacio de trabajo Bromure se ejecuta como ubuntu dentro de una VM que arrancó antes de que la carpeta existiera: ningún byte de clave privada que robar, señuelos donde deberían estar las credenciales, y cada byte de salida por un cable que el anfitrión posee y registra.

Déjalo correr y mira qué encuentra

Bromure Agentic Coding da a cada espacio de trabajo su propia VM Ubuntu bajo el hipervisor de Apple. El agente, su git, su gestor de paquetes y todo lo que esos tres arranquen viven en el invitado. Suelta el zip del colaborador en un espacio de trabajo, deja que el auxiliar se dispare y concédele todo al atacante: antes de la confianza, antes de la autenticación, root en el invitado si quieres.

Leer las claves SSH. No hay ninguna que leer. Las claves privadas de un espacio de trabajo se quedan en tu Mac; la VM recibe un SSH_AUTH_SOCK puenteado por vsock hasta un agente del lado del anfitrión. Ese protocolo tiene una petición que significa “firma este desafío” y ninguna que signifique “dame la clave”.

Barrer el entorno en busca de credenciales. Están todas ahí, con la forma que esperan los validadores, y todas son señuelos. ANTHROPIC_API_KEY guarda un sk-ant-api03-brm-… falso, y lo mismo ocurre con ~/.git-credentials, ~/.aws/config, ~/.kube/config y ~/.docker/config.json. Tus valores reales viven cifrados en tu Mac, y el proxy los sustituye en el cable después de que la petición haya salido de la VM, solo cuando va dirigida al anfitrión para el que se acuñó esa credencial. Ningún interruptor apaga esto. Así es como funcionan las credenciales en la aplicación.

Descargar la segunda etapa. El auxiliar es un cargador, unos cientos de bytes cuyo trabajo es traer la carga real. Esa descarga se topa con el firewall de salida del espacio de trabajo. Pon Tráfico sin coincidencia en Deny y la VM llega a los hosts que has listado y a nada más, aplicado en dos sitios: el conmutador virtual filtra por IP de destino y por nombre de host husmeado en el DNS, en todos los protocolos, y el proxy vuelve a filtrar por nombre de servidor TLS y, para las reglas web, por método HTTP. Un cargador que en su lugar recurra a un registro de paquetes se encuentra con el pipeline de cadena de suministro, donde la barrera de edad rechaza versiones más jóvenes que el corte. Una carga publicada ayer no supera ese listón.

Exfiltrar lo que sí encontró. Ahora algo tiene que cruzar un cable que posee tu Mac. El proxy escanea cada petición saliente contra los señuelos acuñados para el espacio de trabajo, cabeceras y cuerpo, con un autómata de Aho-Corasick. Un señuelo dirigido a un host fuera del ámbito para el que se acuñó es la firma de una máquina que está mandando fuera algo que no debería conocer. El proxy bloquea con un HTTP 451 antes de que un solo byte llegue al destino, pausa la VM y levanta una alerta nombrando la credencial y ambos hosts. Bromure marca entonces el espacio de trabajo como comprometido, y no volverá a arrancar hasta que borres su disco y su home.

Dejar persistencia. Un .bashrc modificado en el invitado dura lo que dejes durar a la VM. Borrar home reinicia /home/ubuntu, Restablecer disco vuelve a clonar el disco de sistema desde la imagen base firmada, y bromure-cli vm run --rm te da un espacio de trabajo que se borra solo cuando la VM se detiene.

La interceptación no tiene variable de entorno que desactivar

Un proxy al que el invitado se adhiere es un proxy que el invitado puede rechazar. El conmutador virtual de Bromure desvía él mismo los flujos del puerto 80 y del puerto 443 de la VM hacia el proxy del anfitrión: no hay HTTPS_PROXY que desactivar, nada en el invitado que lo apague, y el HTTP plano se inspecciona igual que el HTTPS. El proceso que hace la petición aquí nunca leyó la configuración del agente y no es el agente. Es un binario auxiliar que lanzó git, y viaja por el mismo cable que todo lo demás.

La fila existe aunque no exista ninguna llamada a herramienta

El proxy del anfitrión escribe un registro de metadatos por cada petición que sale de la VM, sea cual sea el proceso que la hizo: hora, host, puerto, método, ruta, estado, latencia y bytes medidos antes del intercambio de credencial. Los veredictos del firewall, las decisiones de cadena de suministro y la intermediación de credenciales aterrizan en la Línea de tiempo de seguridad (Ventana → Línea de tiempo de seguridad…), y en un Mac inscrito van al flujo de eventos de la organización como egress.firewall, supply_chain.fetch y credential.exfiltration. Una transcripción de este ataque del lado del agente está en blanco mientras la del cable lleva la petición.

El invitado puede alcanzar una sola cosa en tu Mac. Una carpeta compartida es una ventana viva, de lectura y escritura, a tu sistema de archivos real, y el borrado por compromiso la deja intacta por diseño. Comparte el directorio del proyecto en lugar de tu directorio home, y el find del auxiliar buscando *.pem recorre un home Linux lleno de checkouts y cachés de paquetes en lugar de tus inicios de sesión respaldados por el llavero.

La frontera que no es un paso de la secuencia

Lee juntos los cuatro detalles temporales de GitSpawn — antes del aviso de confianza, antes de la autenticación, antes de la primera tecla, antes de cualquier turno del modelo — y dicen una sola cosa. Cada protección es un paso de una secuencia de arranque, y cada nueva función añade un paso por delante de ella. Cursor lo aprendió en 2025 con un mcp.json suministrado por el repositorio, y otra vez este julio con un comando de preparación de worktree que se ejecutaba antes de su propio aviso de confianza. Ahora le toca a git, en siete productos a la vez, a través de una clave de configuración que lleva años en git.

Un hipervisor no es un paso de esa secuencia. No lee el .git/config. Ninguna refactorización lo reordena y ningún camino de código se exime de él, porque no se ejecuta en el mismo programa que el aviso. La VM estaba corriendo antes de que el zip terminara de descargarse, y sus dos garantías — aquí dentro no hay secretos reales y no hay ruta sin mediar hacia fuera — se sostienen igual en 2.1.193 y en 2.1.196.

Revisa tu .git/config antes de abrir una carpeta que alguien te haya enviado. Manifold tiene razón al decirlo. También lo harás durante dos semanas y luego lo saltarás en el tercer zip de una tarde ajetreada, que es la vida útil de la mayoría de las instrucciones que terminan en “antes de abrirlo”. Instala Bromure Agentic Coding, dale a la carpeta desconocida su propia máquina, y deja que la config ejecute lo que quiera en una habitación donde ejecutarse es todo lo que va a poder hacer.