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

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:

  1. Encontrar regiones alineadas a 64 KiB que el propio sistema de archivos ext4 del contenedor cree libres.
  2. 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.
  3. Leer el dispositivo en bruto en lugar del sistema de archivos.
  4. 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.

Un pool de bloques de 64 KiB, dos clientes, sin puesta a ceroContenedor, cliente A.envcredencialespáginas sqlite, perfil chromiumeliminado — bloques devueltos al poolSandbox, cliente Bescribir 4 KiB en espacio libreleer /dev/vdc, no el fsse queda los otros 60 KiBuna cuenta Workers Paid, nada másPool dm-thin compartido — bloques de 64 KiB, a través de cuentas de clientesskip_block_zeroing — un bloque liberado se reasigna con su contenido intactoEl bloque que le tocó al cliente B4 KiB escritos60 KiB de quien estuviera aquí antesel fs llama a esto espacio libre; el dispositivo en bruto discrepa
Una escritura de 4 KiB, un bloque de 64 KiB. Cloudflare Containers asigna disco desde un pool dm-thin compartido con un tamaño de bloque de 64 KiB, y el pool estaba configurado con skip_block_zeroing, así que un bloque liberado por un cliente volvía al pool con su contenido intacto. Escribir 4 KiB en una región que el propio sistema de archivos ext4 del contenedor creía libre disparaba la asignación de uno de esos bloques; leer el dispositivo en bruto en vez del sistema de archivos devolvía los otros 60 KiB. Los investigadores contaron 2.700 inodos de directorios ajenos distintos en seis placements de producción, y lo que leyeron incluía archivos .env, archivos de credenciales, bases SQLite y perfiles de Chromium.

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.

Un placement multiinquilinocliente Acliente B…y todos los demásun pool de bloques de 64 KiBun bloque liberado se reasigna al siguientelimpio al llegar: un ajuste, no una garantíaqué había en los bloques.env, archivos de credencialesbases sqlite, perfiles chromium2.700 inodos de directorios ajenos en seis placements;residuo en 18 de 24. Ninguna acción del cliente era posible.Bromure Agentic Coding, en tu Macbase.imgUbuntu 24.04, catálogo firmado, idéntico en todas partes, nada tuyo dentroworkspace unodisk.img — clon CoWhome.img — ext4 privadoworkspace dosdisk.img — clon CoWhome.img — ext4 privadoqué hay en esos bloquesghp_… y sk-ant-api03-brm-… — placeholdersLos bloques libres vienen de tu volumen. Los secretos realesestán en el proxy del host y se insertan a la salida.
Dos respuestas a la misma pregunta. En un placement multiinquilino, el disco de un sandbox es un arrendamiento de bloques de un pool compartido con clientes de los que no te hablan, y que esos bloques lleguen limpios es una propiedad de la configuración de la plataforma. En Bromure Agentic Coding el disco de sistema del workspace es un clon APFS copy-on-write de una imagen base pública y firmada, idéntica byte a byte, su home es una imagen ext4 dispersa y privada, los dos son archivos en el directorio propio de ese workspace, y los bloques libres en los que crecen vienen de tu propio volumen. La segunda diferencia es lo que se escribe en esos bloques: los artefactos recuperados en el caso de Cloudflare eran archivos .env y de credenciales, y dentro de un invitado Bromure esos contienen falsificaciones que preservan la estructura, mientras los valores reales se quedan en el proxy del host, en el Mac.

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ú.