Volver a todas las publicaciones
Publicado el · por Renaud Deraison

El aislamiento ejecutó el comando

El 10 de agosto de 2026, Manifold Security publicó un fallo en el agente CLI de Cursor: la opción que arranca el agente en un worktree de git aislado leía un archivo JSON del repositorio que acababas de clonar y pasaba su contenido directamente a un shell, antes del diálogo de Workspace Trust y fuera del sandbox incluso si lo habías activado. Sin modelo, sin inyección de prompts, sin persuasión. Bromure Agentic Coding entrega la misma primitiva de worktree sin ningún sitio donde un repositorio pueda poner un comando, dentro de una VM que existía antes de que llegara el repositorio.

Escribiste la opción que significa “aísla esto”. Esa opción es la que ejecutó el comando de shell del repositorio, como tú, en tu máquina, antes de que nada preguntara si confiabas en ese repositorio.

El 10 de agosto de 2026, Francisco Rosales, de Manifold Security, publicó un hallazgo en el agente de código de línea de comandos de Cursor que The Hacker News recogió tres días después en su repaso ThreatsDay. La prueba de concepto abre una calculadora. Esa es la versión educada de una lista que Manifold detalla: leer ~/.ssh, tomar credenciales de nube del entorno, abrir una shell inversa, escribir persistencia.

Léelo por la entrega, no por la carga útil. Nadie inyectó nada, nadie persuadió a un modelo, ninguna frase ingeniosa se escondía en un README. Un archivo JSON versionado dentro de un repositorio nombró un comando, y la parte del agente cuyo único trabajo es el aislamiento lo ejecutó.

Una opción, un archivo JSON y el orden equivocado

cursor-agent es la versión de terminal del agente de código de Cursor. Soltar un agente sobre tu árbol de trabajo pone nervioso, así que la CLI ofrece contención, documentada en su propia referencia de parámetros:

-w, --worktree [name]    Start in an isolated git worktree at
     ~/.cursor/worktrees/<reponame>/<name>

Un worktree recién creado es un checkout limpio, así que no tiene ninguno de tus artefactos de compilación. Para hacerlo utilizable, la CLI ejecuta por defecto un paso de preparación cuando crea uno. Ese paso lee .cursor/worktrees.json del repositorio que acaba de sacar y pasa el valor de setup-worktree directamente a sh -c:

{
  "setup-worktree": "<any shell command>"
}

La descripción de Manifold de lo que había entre ese valor y el shell tiene tres palabras: “Sin parseo, sin lista de permitidos, sin diálogo”.

.cursor/worktrees.json es un archivo versionado corriente. Llega con un simple git clone, como un README o un archivo de bloqueo. En las compilaciones anteriores a 2026.07.23-e383d2b, la creación del worktree y su preparación ocurrían durante la resolución del espacio de trabajo, y esa resolución terminaba antes que la ruta de código que muestra el diálogo de Workspace Trust. Así que el comando iba primero. En la grabación adjunta al informe de Manifold, la terminal aún está imprimiendo Running worktree setup commands…, se abre la calculadora, y solo entonces aparece el diálogo preguntando si confías en ese directorio.

Cursor es explícito sobre para qué sirve ese diálogo: nada que controle el repositorio debería ejecutarse antes de que lo aceptes. Cuando algo lo hace, la industria tiene un nombre para esa clase de fallo — ejecución previa a la confianza — y Cursor ya ha publicado un arreglo así, en el mismo directorio. En 2025, un .cursor/mcp.json suministrado por el repositorio arrancaba automáticamente el servidor que configuraba en cuanto abrías el proyecto. Eso se convirtió en CVE-2025-64109, calificado como High con CVSS 8.8. Un manejo permisivo de .cursor/cli.json en la misma CLI se convirtió en CVE-2025-61592, también High, también 8.8. La ruta del worktree -w todavía no existía. Llegó cinco meses después de aquel arreglo, con la misma primitiva y sin ninguna barrera.

Manifold lo reportó el 20 de julio. Cursor publicó el reordenamiento el 23 de julio: ahora el diálogo de confianza se muestra primero, y el comando de preparación espera a que el espacio de trabajo sea de confianza. El 29 de julio Cursor cerró el informe como Informative, con el argumento de que la explotación exige que el usuario clone un repositorio controlado por el atacante. La respuesta de Manifold es la que conviene guardar. Clonar un repositorio es para lo que sirve el producto, y también era la condición previa de CVE-2025-64109, el fallo que Cursor calificó con 8.8 y parcheó. Clonar describe la entrega. El defecto está en otra parte.

Antes de 2026.07.23-e383d2b — cursor-agent -w demogit clone.cursor/worktrees.jsonllega, versionadoresolución del espacioworktree creado, el pasode preparación lee el archivosh -c "<su comando>"tu usuario, tu entorno,sandbox: insecure_nonediálogoWorkspace Trust“¿confías?”El diálogo aparecía cuando el comando del repositorio ya se había ejecutado.Tras el arreglo de julio — reordenadodiálogoWorkspace Trustahora, primeropulsas [a] Trustpara un repositorio queclonaste para leerlosh -c "<su comando>"sigue siendo tu usuario, tu entorno,sigue insecure_none — --sandbox enabled no llega ahíOtro orden para las mismas tres cosas, todas decididas por el proceso al que debían poner límites.
Tres controles, todos dentro del proceso al que debían poner límites. En las compilaciones anteriores a 2026.07.23-e383d2b, el comando de preparación del worktree se ejecutaba durante la resolución del espacio de trabajo, por delante del diálogo de confianza, y bajo una política de sandbox fijada en insecure_none, algo que --sandbox enabled no cambia. El arreglo de julio movió el comando detrás de la barrera de confianza. Sigue siendo el mismo shell, con el mismo alcance sobre la misma cuenta de usuario.

El ajuste que activaste no llegaba a esa ruta

Hay una segunda pata, y sobrevivió al arreglo.

El paso de preparación se ejecuta bajo una política de sandbox insecure_none fijada en el código. Ese es el nombre que Cursor le da al valor: en sandbox.json, type acepta workspace_readwrite (el predeterminado), workspace_readonly o insecure_none, que desactiva el sandbox por completo. La ruta de preparación del worktree está clavada en el último. Pasar --sandbox enabled no lo cambia. Manifold, describiendo las compilaciones actuales: actualizar “cierra la ventana previa a la confianza, no el hueco del sandbox”.

Pon las dos patas una al lado de la otra y obtienes algo que sobrevive a este producto concreto. La barrera de confianza era un paso de una secuencia, así que podía caer en el sitio equivocado. El sandbox era un ajuste, así que una ruta de código podía eximirse de él. El worktree llevaba la palabra aislamiento, así que los usuarios leyeron contención en ella. El propio código de Cursor decidió las tres cosas, en momentos que él mismo eligió, dentro del proceso al que debían poner límites. Ese es el mismo fracaso que venció al indicador de seguridad de una biblioteca hace dos semanas, y el que hay detrás de siete fugas de sandbox en las que nada se fugó de un sandbox.

Un aislamiento que comparte tu cuenta de usuario

-w aísla el árbol de trabajo. Le da al agente su propio checkout para que no pisotee tu trabajo sin confirmar, que es un problema real y un arreglo real. Nadie lo construyó para aislar la máquina. El worktree vive en ~/.cursor/worktrees/, bajo tu usuario, con tu entorno, tu ~/.ssh, tu perfil de shell, tus credenciales de nube y tu llavero a una llamada al sistema de distancia. La lista de Manifold de lo que el comando de preparación podría haber hecho se lee como el inventario de ese directorio personal.

Así que: un repositorio que aún no habías leído eligió un comando, y la función que invocaste por seguridad es la que lo ejecutó. Un archivo JSON y un shell, con el modelo mirando desde el banquillo.

Casi toda la conversación sobre seguridad de agentes ha derivado hacia el modelo. Si es crédulo, si se le puede persuadir, si leyó algo que no debía. Esas preguntas importan y les dedicamos muchos artículos. Mientras tanto, la forma más segura de ejecutar código en el portátil de un desarrollador en 2026 es ponerlo en un archivo que una herramienta lee al arrancar, y esperar a que alguien clone.

Un worktree sin ningún sitio donde poner un comando

Bromure Agentic Coding entrega la misma primitiva, porque la primitiva es buena. Pulsa ⇧⌘G en una pestaña de repositorio, escribe un nombre de tarea, elige un agente, y Bromure corta una rama wt/<slug> desde el commit actual, la saca bajo ~/.bromure/worktrees/<repo>/<slug>, abre una pestaña ahí y arranca el agente con tu prompt. Varias tareas, varias ramas, varios agentes, un solo repositorio — el patrón de la flota.

El paso de preparación de Bromure tiene el mismo trabajo que el de Cursor: a un checkout limpio le faltan los archivos ignorados por git que un agente necesita, así que algo tiene que traerlos. La diferencia está en lo que el repositorio puede decir al respecto. El repo puede incluir un archivo .worktreeinclude, y cada línea no comentada dentro de él es una ruta relativa a la raíz del worktree principal. Bromure copia cada una con cp -a, y solo cuando el origen existe y el destino no. Ningún campo de ese archivo contiene un comando, así que no hay ningún sh -c que secuenciar correctamente ni ninguna cuestión de orden que equivocar. Lo peor que puede pedir un .worktreeinclude hostil es que se copie un archivo dentro del checkout. Ese es todo el vocabulario.

La diferencia mayor es dónde ocurre todo esto.

Worktree en tu cuenta de usuariosetup-worktree → sh -celegido por el repositorio, ejecutado como túlo que está a un directorio de distancia~/.ssh/id_ed25519AWS_SECRET_ACCESS_KEY, ANTHROPIC_API_KEY~/.git-credentials, ~/.kube/config~/.zshrc — persistenciasaliente: una conexión corriente desdeuna herramienta de desarrollo corrienteÁrbol de trabajo aislado, máquina compartida.Worktree en un perfil de Bromure.worktreeinclude → cp -asolo rutas — ningún campo guarda un comandoVM Linux desechable · creada antes del clonadoaquí no hay clave privada — ssh-agent por vsockcredenciales de entorno señuelo (brm_…, kubeconfig falso)la persistencia caduca con la tareasaliente: revisado en el proxy del anfitrión —un señuelo fuera de su ámbito → llamada abortada,451 al invitado, VM en pausa, alerta rojaDos ejes: el worktree aísla el árbol, el hipervisor aísla la máquina.
El mismo repositorio, el mismo paso de preparación, dos máquinas distintas. A la izquierda, el worktree es un directorio dentro de tu cuenta: lo que se ejecute ahí alcanza tus claves, tus tokens y tu perfil de shell. A la derecha, el worktree vive dentro de una VM desechable creada antes de clonar el repositorio: las claves privadas nunca entraron en ella, las credenciales a su alcance son señuelos, y la petición saliente que lleva una de ellas se aborta en el proxy del anfitrión antes de que el destino vea un solo byte.

Ejecuta el comando. Mira qué encuentra.

Coge la lista de Manifold y hazla pasar por un perfil, concediéndole al atacante todo: antes de la confianza, sin sandbox, root en el invitado si quieres.

Leer ~/.ssh. No hay nada que leer. Las claves privadas de un perfil se quedan en el anfitrión. La VM recibe un SSH_AUTH_SOCK que apunta a /tmp/bromure-agent.sock, puenteado por vsock hasta un agente por perfil que corre en tu Mac, y Bromure deja a propósito sin conectar tu agente launchd de macOS. El protocolo del agente tiene peticiones para “lista mis claves públicas” y “firma este desafío”. No tiene ninguna petición que signifique “entrega la clave privada”. Activa Require approval to use y cada firma se convierte en un diálogo en el anfitrión con una concesión acotada en el tiempo: cinco minutos, una hora, el resto de la sesión.

Tomar credenciales de nube del entorno. Están ahí, tienen buena pinta, y son falsas. Todo lo que hay en el panel de Credentials de un perfil se inyecta como un marcador de posición y el proxy del anfitrión lo cambia por el valor real sobre el cable: brm_… para claves de API genéricas, un ~/.kube/config sintético con certificados de cliente desechables, un blob base64 falso en ~/.docker/config.json, ~/.git-credentials, material de AWS que se vuelve a firmar del lado del anfitrión y responde InvalidSignatureException a quien intente saltarse el proxy. El robo tiene éxito y no devuelve nada que valga la pena.

Abrir una shell inversa. Ahora el atacante tiene que mover algo por un cable, y el cable es del anfitrión. Cada petición saliente de la VM se contrasta con los señuelos acuñados para el perfil mediante un autómata de Aho-Corasick: cabeceras y cuerpo, la petición entera, no solo las partes que parecen credenciales. Un señuelo camino de un host fuera del ámbito para el que fue acuñado es la firma de una máquina exfiltrando algo que ni siquiera debería conocer. El proxy aborta la llamada hacia arriba antes de que el destino vea un solo byte, devuelve un 451 al invitado, pausa la VM, tiñe de rojo el fotograma congelado y levanta una alerta que nombra la credencial, el host para el que fue acuñada y el host al que fue en su lugar. Tú eliges: apagar, guardar para investigación —imagen de disco, directorio personal, carpetas compartidas, empaquetados y marcados de modo que el perfil no volverá a arrancar sin un borrado— o continuar.

Escribir persistencia. La vida de la máquina dura una tarea. Erase home reinicia /home/ubuntu, y Reset to base vuelve a clonar el disco de sistema del espacio de trabajo.

Mientras tanto, el trabajo que querías sigue adelante. El agente revisa el repositorio, ejecuta las pruebas, abre el pull request. El comando del repositorio se ejecutó, en una habitación donde ejecutarse era todo lo que se le permitía hacer.

La parte que parchear no cubre

Un detalle más del informe de Manifold, y es el que más dura. Cursor no publicó ningún aviso de seguridad para la ruta del worktree y la dejó fuera del changelog de julio, así que el arreglo llegó dentro de una compilación rutinaria. Quien estuviera en una versión afectada no tenía forma de enterarse de que actualizar cerraba una ruta de ejecución previa a la confianza. Hoy tú no tienes forma de saber qué herramienta de tu portátil sigue teniendo una abierta, porque esa ruta siempre es algún archivo que una herramienta lee al arrancar y que nadie ha auditado todavía.

Cursor cerró esta en tres días, lo cual es rápido. Cursor también cerró la versión mcp.json en 2025, y cinco meses después llegó una función nueva con la misma primitiva. Así se ve meter funciones dentro de una secuencia de arranque: cada capacidad añade un paso, y cada paso es una cosa más que ordenar correctamente un jueves.

Un hipervisor no es un paso de esa secuencia. No lee .cursor/worktrees.json, ni .worktreeinclude, ni ninguna otra cosa de tu repositorio. Estaba ahí antes del clonado, no tiene un campo de política que una ruta de código pueda clavar en insecure_none, y su garantía —esta máquina es desechable, estas credenciales son falsas, este cable está vigilado— es la misma el día en que se publica un bypass que el día en que se parchea.

Sigue clonando repositorios que no has leído. Revisar código desconocido es el trabajo, y entregárselo a un agente es el sentido de tener uno. Solo deja de permitir que esa revisión ocurra en la misma cuenta que tus claves SSH. Instala Bromure Agentic Coding, dale a cada tarea su propia rama, su propio checkout y su propia máquina desechable, y la próxima vez que una ruta de arranque resulte ejecutar lo que diga un archivo JSON —y alguna lo hará— se ejecutará en una habitación construida para ello.