El espacio libre no estaba vacío
El 24 de septiembre Cloudflare y Oren Yomtov, de Accomplish, publicaron un fallo entre inquilinos en Cloudflare Containers y Sandboxes. Escribe 4 KiB en un sandbox recién creado, lee el disco en bruto, y los otros 60 KiB del bloque vuelven cargando los archivos .env, los archivos de credenciales y los perfiles de Chromium del inquilino anterior. Nada se escapó de nada: todas las fronteras activas aguantaron, y la fuga estaba en lo que le ocurría a un bloque después de que su dueño terminara con él. Cloudflare vende Sandboxes para ejecutar código generado por modelos de lenguaje. Bromure Agentic Coding ejecuta ese código desde un archivo de imagen en tu propio Mac, y los bloques libres de los que tira son tuyos.
Un sandbox tiene dos trabajos. Contener lo que corre dentro, y olvidar lo que corrió dentro antes. El segundo es una opción de configuración.
Mandas la suite de pruebas del agente a un sandbox en la nube, porque ejecutar
código sin revisar en tu portátil dejó de parecer listo en algún momento del año
pasado. El sandbox arranca en alrededor de un segundo con un sistema de archivos
Linux completo. El agente escribe un archivo pequeño. Debajo, la plataforma le
entrega un bloque de disco de 64 kilobytes, y 60 de esos kilobytes siguen
guardando el .env de otra persona.
No tuviste que escapar de nada para llegar ahí.
Escribe cuatro kilobytes, lee sesenta
El 24 de septiembre Cloudflare publicó un informe conjunto con los investigadores sobre un fallo entre inquilinos en Cloudflare Containers. Oren Yomtov, de Accomplish, lo reportó el 4 de septiembre a través de HackerOne y publicó su propia versión esa misma semana. The Hacker News lo cubrió al día siguiente.
Containers da a cada cliente un disco sacado de un pool compartido, usando thin
provisioning de device-mapper de Linux, con un tamaño de bloque de 64 KiB. El pool
llevaba la opción skip_block_zeroing, que hace lo que dice: dm-thin no limpiaba un
bloque recién asignado antes de entregarlo a un contenedor. Cuando un contenedor se
eliminaba, sus bloques volvían al pool con el contenido intacto, y el pool se
extendía a través de cuentas de clientes distintas.
De ahí en adelante, la técnica son cuatro pasos y ningún exploit:
- Encontrar regiones alineadas a 64 KiB que el propio sistema de archivos ext4 del contenedor cree libres.
- Escribir un único bloque de 4 KiB en cada una. Con eso basta para que dm-thin asigne un bloque físico de 64 KiB sacado del pool compartido.
- Leer el dispositivo en bruto en lugar del sistema de archivos.
- Quedarse con los otros 60 KiB.
La frase de Cloudflare para la primitiva es el bug entero: “una escritura que cubría el bloque completo reemplazaba su contenido anterior, pero una escritura más pequeña cambiaba solo la porción escrita.”
Los bloques recuperados guardaban estructura utilizable. Cloudflare informa de
“estructuras de directorios, páginas de bases de datos y bases SQLite
estructuralmente completas”, y cuenta 2.700 inodos de directorios ajenos
distintos en seis placements de producción, con material residual en 18 de los 24
placements. The Hacker News informa de datos recuperados en 20 de las 22 máquinas
subyacentes, en cuatro continentes. Yomtov enumera lo que leyó en esos bloques:
“listados de directorios, bases SQLite, perfiles de Chromium, archivos .env y
archivos de credenciales.”
Cloudflare establece dos límites que acotan el incidente. Un atacante “no podía seleccionar una víctima concreta ni acceder a un disco activamente conectado”, así que te tocaba quien hubiera estado en esa máquina, no un objetivo elegido. Y Cloudflare construyó firmas de detección a partir de la prueba de concepto de los investigadores, las pasó contra su telemetría histórica de actividad de disco, y no encontró “ninguna evidencia de que este vector de ataque concreto haya sido explotado por nadie más.”
Cloudflare se movió rápido. Reportado a las 15:26 UTC del 4 de septiembre; mala configuración de producción confirmada a las 18:45; arreglo del runtime mergeado a las 21:27; configuración del pool mergeada a las 22:03; despliegue iniciado a las 23:15. Todo eso dentro de un mismo día. El despliegue terminó el 7 de septiembre, los investigadores confirmaron que su prueba de concepto estaba muerta el 14 de septiembre, y Cloudflare borró los últimos snapshots de disco anteriores a la mitigación el 19 de septiembre. Los clientes no tenían ninguna acción que tomar, ni manera de comprobar desde dentro de un sandbox si sus propios bloques habían llegado limpios.
Todas las fronteras activas aguantaron
El título del informe de Yomtov, “Escaping the Cloudflare Sandbox”, se queda corto respecto a lo silencioso que es este bug.
No encontró ningún fallo de seguridad de memoria, ninguna escalada de privilegios y ninguna fuga de contenedor. Todas las fronteras alrededor de un contenedor en ejecución aguantaron según lo diseñado durante todo el incidente. El cliente B no podía leer el contenedor vivo del cliente A, y Cloudflare señala que un sandbox tampoco podía alcanzar un disco activamente conectado.
La fuga estaba en el relevo. Cloudflare liberaba un bloque de un inquilino y lo entregaba al siguiente, y lo que había entre esos dos clientes era un booleano en una configuración de pool.
Los investigadores gastan su atención en lo que el código dentro de un sandbox puede alcanzar ahora mismo, y mucho menos en lo que el último ocupante dejó atrás. Esa segunda pregunta tiene una respuesta aburrida la mayor parte del tiempo: la plataforma pone el bloque a cero. La respuesta se mantiene hasta que un ingeniero apaga la puesta a cero para ganar rendimiento.
Accomplish cuenta esta como su sexta fuga de sandbox publicada desde julio de 2026. Las cinco anteriores: SharedRoot en Claude Cowork, Beltdown en Claude Code, Beltdown2 en la CLI de Cursor, una en el VMM de Docker, y una en el sandbox Codex de OpenAI. Cinco de esas seis ejecutan código que escribió un agente. La conclusión de Yomtov cabe en una línea: “el aislamiento puede fallar de muchas maneras.”
Aquí es exactamente donde debía ir el código de los agentes
Cloudflare Sandboxes corre sobre Containers, así que heredó el bug. Sandboxes es además el producto que Cloudflare vende para eso sobre lo que este blog no deja de escribir. Según la propia documentación de Cloudflare, el SDK existe para “ejecutar código no confiable de forma segura en entornos aislados”, sirve para “ejecutar código generado por Grandes Modelos de Lenguaje de manera segura y fiable”, y encaja con “agentes de IA, asistentes de código y sistemas autónomos que necesitan ejecutar código no confiable.” Cada sandbox corre “en su propio contenedor aislado con un entorno Linux completo, ofreciendo fronteras de seguridad fuertes y manteniendo el rendimiento.”
Cada cláusula de eso es algo razonable de querer, y describe la VM de workspace de Bromure más o menos igual de bien que describe un Cloudflare Sandbox. La diferencia es quién más está en la máquina.
Sacar la ejecución de los agentes de tu portátil reubica una decisión de confianza en lugar de eliminarla. Ahora confías en un asignador de pool que no puedes ver, en un placement que no elegiste, al lado de inquilinos que nadie va a nombrarte. Ese puede ser el intercambio correcto. Hazlo a propósito, y haz primero la pregunta del residuo: de dónde vienen mis bloques libres, y qué había en ellos.
El disco es un archivo en tu Mac
Bromure Agentic Coding responde a esa pregunta no teniendo pool.
Cada workspace corresponde a exactamente una VM. Dos workspaces nunca comparten una
VM, y un workspace nunca tiene dos. La VM arranca desde dos discos virtuales que son
archivos corrientes en el directorio propio de ese workspace, en tu Mac. disk.img
es el disco de sistema, creado en el primer lanzamiento como un clon APFS
copy-on-write de la imagen base local de Ubuntu: instantáneo, y gratis hasta que el
invitado escribe. home.img es una imagen ext4 dispersa y privada que contiene
/home/ubuntu, conectada como un segundo dispositivo virtio-blk.
La técnica de cuatro pasos no tiene dónde aterrizar aquí. Un agente en el invitado puede leer el dispositivo de bloques en bruto en vez del sistema de archivos, ya que ahí dentro es root, y eso está bien. Alcanza un archivo de imagen que pertenece a un workspace. Los bloques en los que ese archivo crece vienen del volumen APFS de tu propio Mac, y el ocupante anterior de un bloque libre en tu Mac eras tú.
Un objeto se comparte entre workspaces, y “no se comparte nada” sería la afirmación
equivocada, así que nombrémoslo: todo disco de workspace se clona de base.img.
Ubuntu 24.04, 24 GB lógicos y de 6 a 8 GB físicos, obtenido contra un catálogo de
imágenes firmado que lleva una firma ed25519 hecha con la misma clave que firma las
actualizaciones de la app, cubriendo la identidad de la imagen, su SHA-256 y cada
comando de postinstall, con protección contra rollback y rechazo de catálogos sin
firmar. Es idéntico byte a byte en cada instalación y no contiene nada tuyo. Ese es
el tipo correcto de cosa que compartir: un artefacto público cuyo trabajo es ser
idéntico en todas partes.
Y el residuo no valdría nada
La disposición de los bloques es la mitad de la respuesta. La otra mitad es lo que
un agente de código escribe en disco, que es lo que hizo que este bug mereciera un
reporte: archivos .env, archivos de credenciales, perfiles de Chromium.
Dentro de un workspace Bromure esos archivos existen, y las herramientas que los
leen funcionan como siempre, pero los valores que contienen son falsos. Ningún
archivo, variable de entorno ni proceso dentro de la VM guarda nunca una clave de
API real, un token OAuth, un secreto de AWS o una clave privada SSH. El invitado
recibe en su lugar placeholders que preservan la estructura:
sk-ant-api03-brm-…, un token de GitHub con forma ghp_, brm_…,
deterministas por instalación, para que los validadores del lado del cliente queden
satisfechos y ninguna herramienta piense nunca que una clave rotó entre sesiones.
Los valores reales viven en el mapa de sustitución en memoria del proxy del host, en
el Mac, cada uno asociado al host de destino al que pertenece, y se sustituyen a la
salida con un alcance exacto-o-subdominio, nunca por subcadena. Las peticiones de
AWS se vuelven a firmar en el lado del host: el invitado firma con un secreto falso,
el host quita esa firma y firma de nuevo. SSH es solo-firma sobre un puente vsock, y
los bytes de las claves privadas nunca cruzan al invitado.
Bromure trata el perfil de Chromium por separado. El panel de navegador que el agente pilota corre en su propia VM Alpine efímera, un clon copy-on-write en el directorio temporal que la app elimina cuando cierras el panel. Conservar los inicios de sesión es opt-in, y eso pone el perfil en un disco cifrado por workspace.
Si algún fallo futuro expusiera los bloques de un invitado Bromure, quien lo leyera se iría con un árbol de directorios, código fuente que ya tienes, y un archivo de credenciales que no se puede gastar en ningún sitio.
La desechabilidad como verbo
Los workspaces son persistentes a propósito. Programar necesita estado duradero: repos clonados, cachés de paquetes, toolchains, historial del shell. Así que Bromure controla lo que la VM tiene permitido guardar en lugar de destruirla cada noche, y borrar un disco es algo que pides. La app es explícita sobre cada forma de pedirlo:
Restablecer a la base
Elimina disk.img para que el siguiente lanzamiento vuelva a clonar una copia
fresca de la imagen base. El diálogo nombra lo que se va: todo lo que instalaste
con apt install, y tus ediciones de /etc, /var, /usr, /opt. El home
sobrevive.
Borrar el home, eliminar el workspace
Borrar el home tira /home/ubuntu y el par de claves generado. Eliminar el
workspace quita todo el directorio de perfil, las dos imágenes, el archivo de
secretos cifrado, cada checkpoint y el material SSH del lado del host, y después
libera su dirección MAC reservada.
Desechables y checkpoints
bromure-cli vm run --rm te da un workspace al estilo docker, eliminado cuando
la VM se detiene. Cada arranque exitoso deja un checkpoint copy-on-write del
disco y del home, tomado solo después de un arranque que produjo una terminal
funcionando, así que cada uno es un arranque demostrado.
El borrado forzoso
Si el proxy pilla una credencial placeholder camino de un sitio al que no pertenece, Bromure marca el workspace como comprometido, y no volverá a arrancar hasta que borres su disco y su home. Conservas tus ajustes, tokens y claves SSH, y el borrado nunca toca las carpetas compartidas del host.
Ese conjunto de verbos es la diferencia entre un borrado que pediste y un borrado que supusiste.
Pregunta qué dejó el último inquilino
Cloudflare encontró la mala configuración, la arregló en nueve horas, limpió los pools y los snapshots, construyó firmas de detección para comprobar el pasado, y publicó el tamaño de bloque y el nombre de la flag. Lee eso como un proveedor gestionando bien un mal día.
“Sandbox” nombra una esperanza sobre una frontera, y esta frontera resultó ser un ciclo de vida de recurso más que un muro. La respuesta de la industria a los agentes que ejecutan código sin revisar ha sido mover la ejecución a otro sitio y confiar en que ese otro sitio llega limpio. Llega, la mayor parte del tiempo. No puedes comprobarlo desde dentro del sandbox, ni esta semana ni ninguna.
Ejecuta el código en infraestructura que puedas ver y te toca responder tú mismo la pregunta del residuo: un archivo de imagen por workspace, bloques libres de tu propio volumen, un objeto compartido y es una imagen base pública firmada, y nada en ese disco que merezca la pena leer si alguna vez se filtrara. Borrarlo es una entrada de menú.