← Volver a todas las publicaciones
Publicado el · por Renaud Deraison

El último paso fue un prompt

El 18 de septiembre Hacktron publicó la cadena que usó para llegar al monorepo privado de OpenAI. Empieza con una imagen subida a un foro público y un desbordamiento de heap en un decodificador de imágenes cuya corrección aguas arriba no llevaba CVE alguno. Termina con los investigadores enviando un prompt a la cuenta Codex de un empleado, y el agente abriendo el pull request. Nada se inyectó y nada falló; el agente hizo lo que pidió quien tenía la sesión. Bromure Agentic Coding decide esa escritura en su Mac, fuera de la sesión.

El último paso de esta cadena no necesitó ningún exploit. Los investigadores tenían el agente de codificación de un empleado, así que le pidieron que abriera un pull request en el monorepo privado, y abrió uno.

A las seis de la mañana UTC del 25 de julio, tres investigadores de Hacktron tenían ejecución de código en el foro público de la comunidad de OpenAI. A primera hora de la tarde había un pull request numerado 1186742 en openai/openai, el monorepo privado, y no habían escrito una línea de código para ponerlo ahí. Se lo pidieron a un agente, y el agente lo presentó.

Hacktron publicó la cadena el 18 de septiembre, y merece leerse eslabón por eslabón, porque cada eslabón es corriente. Los investigadores son Harsh Jaiswal, Mohan Pedhapati y Rahul Maini. The Hacker News lo cubrió al día siguiente.

Una imagen, una conversión y un analizador

La vía de entrada fue la subida de imágenes del foro. Discourse comprueba las imágenes subidas con una biblioteca de Ruby llamada FastImage, que lee lo suficiente de la cabecera de un archivo para deducir sus dimensiones y su formato. FastImage no entiende HEIF, el contenedor en el que llega una foto de iPhone, así que Discourse pasaba esos archivos al comando magick de ImageMagick para convertirlos. ImageMagick decodifica HEIC con libheif, que es C.

Esa es toda la condición de entrada. Un formato que el comprobador seguro no reconocía fue a parar a un conversor que llamaba a un analizador escrito en un lenguaje donde un campo de longitud malformado es un problema de seguridad de memoria. Hacktron encontró un desbordamiento de búfer de heap en la decodificación HEIC y sacó de ahí primitivas de lectura y escritura fuera de límites.

El papeleo es peor que el fallo. Aguas arriba habían corregido ese desbordamiento el año anterior y nunca etiquetaron el commit como corrección de seguridad. No recibió CVE, ni aviso, ni una línea de changelog que un escáner de dependencias pudiera leer, así que Debian nunca lo retroportó, y la imagen Docker de Discourse distribuía libheif 1.19.7 sobre Debian 12 con el fallo intacto. Ningún escáner del mundo tenía motivo para señalar esa versión. Debian emitió el DSA-6417-1 más tarde y el asunto lleva hoy el CVE-2026-32882, asignado después de que alguien lo explotara.

No se puede escanear un fallo que nadie ha registrado. Lo que sí se puede decidir de antemano es dentro de qué está el analizador cuando se cae.

De una subida a un pull requestel análisisuna subida HEICpuesta en un foro públicosin privilegios de cuentaFastImagela comprobación segurano admite HEIFmagickconvierte en su lugarImageMagick llama a libheiflibheif 1.19.7desbordamiento de heap, R/W OOBcorregido, sin CVE ni backportel pivoteejecución de códigocommunity.openai.com06:00 UTC, 25 de julioun fallo de SSOuna sesión del foro pasa a sertoma de cuenta, sin interacciónel Codex de un empleadoconectado a la org de GitHubya autorizado a escribirsin exploitun promptPR #1186742monorepo privado, openai/openai
Cada eslabón de la cadena es corriente. FastImage no admite HEIF, así que el archivo va a ImageMagick, que llama a libheif, donde había un desbordamiento de heap cuya corrección aguas arriba no llevaba CVE y nunca llegó a Debian. Un fallo de SSO aparte convirtió la ejecución de código en el foro en acceso a la cuenta, y el acceso a la cuenta en un pull request en el monorepo privado. Solo el paso final no necesitó exploit: los investigadores enviaron un prompt a un agente de codificación que ya estaba autorizado a hacer push.

El exploit llevó una tarde

Hacktron empezó a mirar la cadena de subida el 23 de julio. El 24 de julio fueron a por el desbordamiento con Claude Opus 4.8 que, según cuentan, batalló a lo largo de varias sesiones para producir algo que funcionara contra la aleatorización del espacio de direcciones, la defensa que baraja el mapa de memoria de un proceso para que un atacante no pueda predecir dónde está nada. Convertir un desbordamiento de heap en ejecución fiable frente a ASLR es la parte difícil, y es donde este tipo de investigación se atasca una semana.

Anthropic publicó Opus 5 esa misma tarde. Hacktron abrió una sesión nueva y tres horas después tenía un exploit ARM64 funcionando contra un Mac local. Le pidieron al modelo que lo portara a la configuración x86-64 y jemalloc sobre la que corre Discourse, y confirmaron ejecución local de código mediante una subida de imagen a las seis de la mañana siguiente. Después pusieron a Claude en un bucle autónomo contra su propia instancia de Discourse Cloud. A las diez también tenía ejecución de código allí, y leyó /etc/hosts para demostrarlo.

Son cuidadosos con lo que eso significa, y la salvedad cabe en cualquier lectura del caso:

Esto no fue hacking plenamente autónomo. La pericia humana siguió siendo necesaria para dirigir sesiones, validar resultados y gestionar la divulgación.

Justo. Tres investigadores competentes apuntaron la herramienta, y su competencia no cambió de la noche a la mañana. El fallo tampoco cambió. Entre aquellas dos sesiones la única variable nueva era el modelo, y llevó un trabajo de explotación de varios intentos fallidos a tres horas. La campaña de la que esto formaba parte le costó a Hacktron menos de 3.000 dólares en tokens y unas pocas horas de tiempo humano.

Ese número es el dato de planificación. Un fallo que nunca recibió CVE, alojado en una dependencia transitiva de un conversor de archivos, se convierte hoy en una primitiva fiable por el precio de un portátil. Esperar al aviso es una esperanza, y nadie le gana a ese reloj leyendo changelogs más deprisa.

El último paso fue un prompt

La ejecución de código en el foro no le dio a Hacktron nada que a OpenAI le importara. Eso lo hizo un segundo problema. OpenAI había cableado su inicio de sesión único de modo que una sesión de foro comprometida se convertía en la toma sin interacción de la cuenta de ChatGPT y Codex de cualquier empleado que usara el foro. En palabras de Hacktron, el problema de SSO es lo que convirtió un compromiso del foro en acceso a ChatGPT y Codex.

El Codex de un empleado estaba conectado a la organización de la empresa en GitHub, así que los investigadores hicieron lo evidente. Enviaron a esa cuenta un prompt pidiéndole que abriera un pull request en el monorepo interno, y el PR 1186742 apareció en openai/openai. Entonces se detuvieron, sin leer código fuente, y presentaron el informe.

Lea ese paso otra vez. Alguien con una sesión válida le pidió al agente que hiciera algo corriente, y el agente lo hizo con el acceso de escritura que ya tenía. Sin inyección, sin jailbreak, sin entrada malformada. Todo control que alguien hubiera construido estaba detrás de esa sesión: el inicio de sesión, la pertenencia a la organización, los permisos del repositorio. Una vez que un atacante tenía la sesión, los tenía todos.

Un agente de codificación es una concesión permanente de acceso de escritura a sus repositorios, prendida a una conversación. Quien alcance la conversación hereda la concesión. Eso vale para Codex en un navegador, y vale para Claude Code, Codex CLI o Gemini CLI en un portátil, donde el agente lleva un token ghp_ real en ~/.git-credentials y un GH_TOKEN real en su entorno. En un portátil las vías de entrada se multiplican: un archivo envenenado en un repositorio, un comentario de issue, un script postinstall de un paquete, una terminal secuestrada.

OpenAI parcheó el foro en unas catorce horas. El informe entró esa mañana y OpenAI confirmó la corrección a las 22:49 UTC del mismo día. Discourse publicó su propia corrección durante el fin de semana y el aviso GHSA-vhm9-85gw-x335 el 28 de julio, y la recompensa ascendió a 6.500 dólares. La pieza de esta forma que está sobre su propio escritorio es el agente.

Dónde se decide la escritura

Bromure Agentic Coding guarda la respuesta a quién tiene permiso para hacer push en su Mac, fuera de la sesión del agente, porque la sesión es justo lo que un atacante intenta alcanzar. Allí pasan dos cosas.

Primero, el entorno del agente no contiene ningún token que heredar. La VM del espacio de trabajo recibe un señuelo: ghp_ más 36 caracteres, con una forma tal que gh y git lo aceptan sin quejarse, derivado de su token real con HKDF-SHA256 y una sal por instalación, de modo que nunca parece rotar entre sesiones. Su token real vive cifrado en el anfitrión, y el proxy del anfitrión lo sustituye en el cable después de que la petición ha salido de la VM, y solo cuando el destino coincide con el anfitrión para el que se acuñó ese token. Como dice el manual: ningún archivo, variable de entorno o proceso dentro de la VM contiene jamás una clave de API real, un token OAuth, un secreto de AWS o una clave SSH privada. Lleve ese señuelo a donde no corresponde y el proxy responde HTTP 451 sin reenviar un byte, pausa la VM y escribe una fila roja en la Security Timeline.

La segunda es la que esta historia pide. Cada credencial de forja git lleva una política de escritura, fijada por espacio de trabajo en el panel Guardrails, y los espacios nuevos arrancan en Preguntar antes de escribir. Las lecturas pasan sin comentario. Un git push llega al cable como git-receive-pack y cuenta como escritura, y un POST a la API de pull requests también. Cuando llega uno, el proxy retiene la petición y abre un diálogo en su Mac que lleva la operación literal en vez de un resumen: el método y la ruta. Obtiene Permitir 15 minutos, Permitir una vez, Permitir el resto de la sesión o No permitir. Una negativa llega al agente como un error de API corriente que puede reportar, no como una conexión colgada.

La descripción que el propio panel da de dónde corre esto es la frase que importa: aplicado en el proxy, de modo que un agente comprometido dentro de la VM no puede saltárselo.

Sea quien sea el que conduzca al agente — usted, una inyección de prompt enterrada en un archivo de fixtures, o alguien que tiene una sesión que le quitó —, la escritura sale por el mismo proxy y se clasifica igual. El diálogo en su Mac es la autorización, y un atacante con su sesión no tiene su Mac.

Un agente en el portátilla sesión del agentealcanzable por usted, por una inyección, porquien se lleve la sesióncredenciales reales, al alcanceGH_TOKEN · ~/.git-credentialslegibles por cualquier proceso de la máquinaPOST /repos/org/repo/pulls → GitHubla sesión lleva la autoridadUn agente en un espacio Bromurela sesión del agente, dentro de la VMghp_ + 36 caracteres, un señuelonada real que heredarel switch desvía :80 y :443el proxy del anfitrión lo clasificaPOST /repos/org/repo/pulls = escrituragit-receive-pack también cuentaun diálogo en su Mac, con la llamada literal15 minutos · una vez · la sesión · no permitirel Mac lleva la autoridad
En un portátil de desarrollo el agente lleva un token real, así que basta un prompt y la sesión lleva la autoridad. En un espacio de trabajo Bromure la VM lleva un señuelo, el proxy del anfitrión sustituye el token real a la salida, y el guardarraíl clasifica la escritura antes de que llegue a GitHub. Preguntar antes de escribir, el valor por defecto de los espacios nuevos, abre un diálogo en su Mac con el método y la ruta literales. Quienquiera que tenga la sesión, no tiene el Mac.

El problema del analizador también llega a su escritorio

La otra mitad de la cadena también se generaliza, y poca gente lo ha metido en sus cuentas.

Un agente de codificación se pasa el día leyendo archivos que no escribió: fixtures de prueba que venían con el repositorio, una imagen adjunta a un informe de fallo, una captura de pantalla que usted pegó en la conversación, el PDF de una especificación, un tarball de una página de releases, una fuente, un vídeo que el front end debe reproducir. Manejarlos implica invocar magick, ffmpeg, pdftotext o unzip, o una biblioteca de imágenes enlazada a Python, y cada uno de ellos es un analizador en C o C++ comiendo bytes que eligió otra persona. Esa es la condición que dejó que una subida de foro llegara a libheif. Un agente la encuentra más a menudo que usted, porque nunca duda antes de abrir algo.

La detección no tiene respuesta aquí. El fallo de libheif no llevaba CVE en el momento en que importaba, así que ningún escáner, lista de permitidos ni fijación de versión habría dicho una palabra. Puede elegir el radio de la explosión, y nada más.

En un espacio de trabajo Bromure ese decodificador corre dentro de un invitado Ubuntu bajo el Virtualization.framework de Apple, una frontera de hipervisor y no un sandbox de proceso, en un invitado que Bromure trata como no confiable por construcción. Un desbordamiento de heap en un analizador de imágenes le compra al atacante una shell en una VM Linux cuyas credenciales son señuelos y cuya ruta a internet es un conjunto ordenado de reglas en el anfitrión. El conmutador virtual aplica ese conjunto por IP de destino y por nombre de host espiado del DNS, en TCP y en UDP, y el proxy lo aplica otra vez por nombre de servidor TLS y método HTTP. Ponga el tráfico no emparejado en Denegar y la shell se queda con una lista de permitidos y nada más. El conmutador desvía :80 y :443 hacia el propio proxy, así que no hay variable de proxy que la shell pueda desactivar. Las ediciones de reglas alcanzan las sesiones en curso sin reiniciar, y cada veredicto cae como una fila de Firewall en la Security Timeline.

Dé por hecho que el analizador se cae

Este fallo no llevaba CVE en el momento que contaba. Planifique con que el decodificador pierda, y decida de antemano dentro de qué está cuando pase.

Una sesión no es una autorización

Quien alcance la conversación hereda el acceso de escritura del agente. Ponga la decisión de un push donde la conversación no llegue.

No deje nada que robar en la VM

Un señuelo que las herramientas aceptan no vale nada aguas arriba, y vale menos que nada para un atacante que se lo lleve a otra parte.

Póngale precio al exploit

Tres horas y unos miles de dólares en tokens. Leer avisos más deprisa no responde a ese número.

El informe de Hacktron es buena investigación, y honesto sobre cuánto tuvo que guiar todavía un humano. El final es la parte que seguirá siendo cierta el año que viene. Toda defensa de la cadena estaba detrás de una identidad, y en cuanto una sesión que portaba esa identidad cambió de manos, ninguna de ellas tuvo ya opinión alguna. Alguien se lo pidió al agente, y el agente obedeció.

Si su agente puede hacer push, pregunte dónde vive el permiso para hacer push, y si un atacante que se llevara todo lo que el agente tiene se llevaría también eso. En un portátil, se lo llevaría. Mueva esa única decisión fuera de la máquina en la que corre el agente, y el último paso de esta cadena tendrá dónde pararse.