← Voltar para todas as publicações
Publicado em · por Renaud Deraison

O espaço livre não estava vazio

Em 24 de setembro a Cloudflare e Oren Yomtov, da Accomplish, publicaram uma falha entre inquilinos no Cloudflare Containers e no Sandboxes. Escreva 4 KiB num sandbox recém-criado, leia o disco bruto, e os outros 60 KiB do bloco voltam carregando os arquivos .env, os arquivos de credenciais e os perfis do Chromium do inquilino anterior. Nada escapou de nada: todas as fronteiras ativas resistiram, e o vazamento estava no que acontecia com um bloco depois que seu dono terminava de usá-lo. A Cloudflare vende Sandboxes para executar código gerado por modelos de linguagem. O Bromure Agentic Coding executa esse código a partir de um arquivo de imagem no seu próprio Mac, e os blocos livres de onde ele puxa são seus.

Um sandbox tem duas tarefas. Conter o que roda dentro dele, e esquecer o que rodou dentro dele antes. A segunda é uma opção de configuração.

Você manda a suíte de testes do agente para um sandbox na nuvem, porque rodar código não revisado no seu laptop deixou de parecer inteligente em algum momento do ano passado. O sandbox sobe em cerca de um segundo com um sistema de arquivos Linux completo. O agente escreve um arquivo pequeno. Por baixo, a plataforma lhe entrega um bloco de disco de 64 kilobytes, e 60 desses kilobytes ainda guardam o .env de outra pessoa.

Você não precisou escapar de nada para chegar lá.

Escreva quatro kilobytes, leia sessenta

Em 24 de setembro a Cloudflare publicou um relato conjunto com os pesquisadores sobre uma falha entre inquilinos no Cloudflare Containers. Oren Yomtov, da Accomplish, reportou-a em 4 de setembro pelo HackerOne e publicou a sua própria versão na mesma semana. O The Hacker News cobriu o caso no dia seguinte.

O Containers dá a cada cliente um disco tirado de um pool compartilhado, usando o provisionamento fino do device-mapper do Linux, com tamanho de bloco de 64 KiB. O pool carregava a opção skip_block_zeroing, que faz exatamente o que o nome diz: o dm-thin não limpava um bloco recém-alocado antes de entregá-lo a um contêiner. Quando um contêiner era excluído, seus blocos voltavam ao pool com o conteúdo intacto, e o pool atravessava contas de clientes diferentes.

Daí em diante, a técnica são quatro passos e nenhum exploit:

  1. Encontrar regiões alinhadas em 64 KiB que o próprio sistema de arquivos ext4 do contêiner considera livres.
  2. Escrever um único bloco de 4 KiB em cada uma. Isso é o bastante para o dm-thin alocar um bloco físico de 64 KiB tirado do pool compartilhado.
  3. Ler o dispositivo bruto em vez do sistema de arquivos.
  4. Ficar com os outros 60 KiB.

A frase da própria Cloudflare para a primitiva é o bug inteiro: “uma escrita que cobria o bloco todo substituía o conteúdo anterior, mas uma escrita menor alterava apenas a porção escrita.”

Os blocos recuperados guardavam estrutura utilizável. A Cloudflare relata “estruturas de diretórios, páginas de banco de dados e bancos SQLite estruturalmente completos”, e conta 2.700 inodes de diretórios alheios distintos em seis placements de produção, com material residual em 18 dos 24 placements. O The Hacker News relata dados recuperados em 20 das 22 máquinas subjacentes, em quatro continentes. Yomtov lista o que leu naqueles blocos: “listagens de diretórios, bancos SQLite, perfis do Chromium, arquivos .env e arquivos de credenciais.”

A Cloudflare afirma dois limites que delimitam o incidente. Um atacante “não podia selecionar uma vítima específica nem acessar um disco ativamente conectado”, então você pegava quem tivesse estado naquela máquina, não um alvo escolhido. E a Cloudflare construiu assinaturas de detecção a partir da prova de conceito dos pesquisadores, rodou-as contra sua telemetria histórica de atividade de disco, e não encontrou “nenhuma evidência de que este vetor de ataque específico tenha sido explorado por mais alguém.”

A Cloudflare agiu rápido. Reportado às 15:26 UTC de 4 de setembro; má configuração de produção confirmada às 18:45; correção do runtime mesclada às 21:27; configuração do pool mesclada às 22:03; rollout iniciado às 23:15. Tudo isso dentro de um único dia. O rollout terminou em 7 de setembro, os pesquisadores confirmaram que sua prova de conceito estava morta em 14 de setembro, e a Cloudflare apagou os últimos snapshots de disco anteriores à mitigação em 19 de setembro. Os clientes não tinham nenhuma ação a tomar, e nenhuma forma de verificar de dentro de um sandbox se os seus próprios blocos tinham chegado limpos.

Um pool de blocos de 64 KiB, dois clientes, nenhuma limpezaContêiner, cliente A.envcredenciaispáginas sqlite, perfil chromiumexcluído — blocos devolvidos ao poolSandbox, cliente Bescrever 4 KiB no espaço livreler /dev/vdc, não o fsfica com os outros 60 KiBuma conta Workers Paid, nada maisPool dm-thin compartilhado — blocos de 64 KiB, atravessando contas de clientesskip_block_zeroing — um bloco liberado é realocado com o conteúdo intactoO bloco entregue ao cliente B4 KiB escritos60 KiB de quem estava aqui anteso fs chama isso de espaço livre; o dispositivo bruto discorda
Uma escrita de 4 KiB, um bloco de 64 KiB. O Cloudflare Containers aloca disco a partir de um pool dm-thin compartilhado com tamanho de bloco de 64 KiB, e o pool estava configurado com skip_block_zeroing, então um bloco liberado por um cliente voltava ao pool com o conteúdo intacto. Escrever 4 KiB numa região que o próprio sistema de arquivos ext4 do contêiner acreditava estar livre disparava a alocação de um desses blocos; ler o dispositivo bruto em vez do sistema de arquivos devolvia os outros 60 KiB. Os pesquisadores contaram 2.700 inodes de diretórios alheios distintos em seis placements de produção, e o que leram incluía arquivos .env, arquivos de credenciais, bancos SQLite e perfis do Chromium.

Todas as fronteiras ativas resistiram

O título do relato de Yomtov, “Escaping the Cloudflare Sandbox”, subestima o quanto esse bug é silencioso.

Ele não encontrou nenhum bug de segurança de memória, nenhuma escalada de privilégios e nenhuma fuga de contêiner. Todas as fronteiras em volta de um contêiner em execução resistiram como projetado durante todo o incidente. O cliente B não conseguia ler o contêiner vivo do cliente A, e a Cloudflare observa que um sandbox também não conseguia alcançar um disco ativamente conectado.

O vazamento estava na passagem de bastão. A Cloudflare liberava um bloco de um inquilino e o entregava ao próximo, e a coisa que separava esses dois clientes era um booleano numa configuração de pool.

Pesquisadores gastam sua atenção no que o código dentro de um sandbox pode alcançar agora, e muito menos no que o último ocupante deixou para trás. Essa segunda pergunta tem uma resposta chata na maior parte do tempo: a plataforma zera o bloco. A resposta vale até um engenheiro desligar o zeramento em nome da vazão.

A Accomplish conta esta como a sua sexta fuga de sandbox publicada desde julho de 2026. As cinco anteriores: SharedRoot no Claude Cowork, Beltdown no Claude Code, Beltdown2 no CLI do Cursor, uma no VMM do Docker, e uma no sandbox Codex da OpenAI. Cinco dessas seis executam código que um agente escreveu. A conclusão de Yomtov cabe em uma linha: “o isolamento pode falhar de muitas maneiras.”

Era exatamente para cá que o código dos agentes devia ir

O Cloudflare Sandboxes roda sobre o Containers, então herdou o bug. O Sandboxes é também o produto que a Cloudflare vende para a coisa sobre a qual este blog não para de escrever. Pela própria documentação da Cloudflare, o SDK existe para “executar código não confiável com segurança em ambientes isolados”, serve para “executar código gerado por Grandes Modelos de Linguagem de forma segura e confiável”, e se adequa a “agentes de IA, assistentes de código e sistemas autônomos que precisam executar código não confiável.” Cada sandbox roda “no seu próprio contêiner isolado com um ambiente Linux completo, oferecendo fronteiras de segurança fortes e mantendo o desempenho.”

Toda cláusula disso é uma coisa razoável de se querer, e descreve a VM de workspace do Bromure mais ou menos tão bem quanto descreve um Cloudflare Sandbox. A diferença é quem mais está na máquina.

Mover a execução dos agentes para fora do seu laptop realoca uma decisão de confiança em vez de removê-la. Agora você confia num alocador de pool que não pode ver, num placement que não escolheu, ao lado de inquilinos que ninguém vai nomear para você. Isso pode ser a troca certa. Faça-a de propósito, e faça primeiro a pergunta do resíduo: de onde vêm os meus blocos livres, e o que havia neles.

O disco é um arquivo no seu Mac

O Bromure Agentic Coding responde a essa pergunta não tendo um pool.

Cada workspace corresponde a exatamente uma VM. Dois workspaces nunca compartilham uma VM, e um workspace nunca tem duas. A VM inicializa a partir de dois discos virtuais que são arquivos comuns no diretório próprio daquele workspace, no seu Mac. disk.img é o disco de sistema, criado no primeiro lançamento como um clone APFS copy-on-write da imagem base local do Ubuntu: instantâneo, e gratuito até o convidado escrever. home.img é uma imagem ext4 esparsa e privada que guarda /home/ubuntu, conectada como um segundo dispositivo virtio-blk.

A técnica de quatro passos não tem onde aterrissar aqui. Um agente no convidado pode ler o dispositivo de bloco bruto em vez do sistema de arquivos, já que ele é root ali dentro, e isso está tudo bem. Ele alcança um arquivo de imagem que pertence a um workspace. Os blocos nos quais esse arquivo cresce vêm do volume APFS do seu próprio Mac, e o ocupante anterior de um bloco livre no seu Mac era você.

Um objeto é compartilhado entre workspaces, e “nada é compartilhado” seria uma afirmação errada, então vamos nomeá-lo: todo disco de workspace é clonado de base.img. Ubuntu 24.04, 24 GB lógicos e 6 a 8 GB físicos, obtido contra um catálogo de imagens assinado que carrega uma assinatura ed25519 feita com a mesma chave que assina as atualizações do app, cobrindo a identidade da imagem, seu SHA-256 e cada comando de postinstall, com proteção contra rollback e recusa de catálogos não assinados. É idêntico byte a byte em cada instalação e não contém nada seu. Esse é o tipo certo de coisa para compartilhar: um artefato público cuja função é ser idêntico em todo lugar.

Um placement multi-inquilinocliente Acliente B…e todos os demaisum pool de blocos de 64 KiBum bloco liberado é reemitido para quem vier depoislimpo na chegada: um ajuste, não uma garantiao que havia nos blocos.env, arquivos de credenciaisbancos sqlite, perfis chromium2.700 inodes de diretórios alheios em seis placements;resíduo em 18 de 24. Nenhuma ação do cliente era possível.Bromure Agentic Coding, no seu Macbase.imgUbuntu 24.04, catálogo assinado, idêntico em todo lugar, nada seu dentroworkspace umdisk.img — clone CoWhome.img — ext4 privadoworkspace doisdisk.img — clone CoWhome.img — ext4 privadoo que há nesses blocosghp_… e sk-ant-api03-brm-… — placeholdersOs blocos livres vêm do seu volume. Os segredos reais ficamno proxy do host e entram na saída.
Duas respostas para a mesma pergunta. Num placement multi-inquilino, o disco de um sandbox é um arrendamento de blocos de um pool compartilhado com clientes sobre os quais não lhe contam, e se esses blocos chegam limpos é uma propriedade da configuração da plataforma. No Bromure Agentic Coding o disco de sistema do workspace é um clone APFS copy-on-write de uma imagem base pública e assinada, idêntica byte a byte, seu home é uma imagem ext4 esparsa e privada, os dois são arquivos no diretório próprio daquele workspace, e os blocos livres nos quais eles crescem vêm do seu próprio volume. A segunda diferença é o que é escrito nesses blocos: os artefatos recuperados no caso Cloudflare eram arquivos .env e de credenciais, e dentro de um convidado Bromure esses guardam falsificações que preservam a estrutura, enquanto os valores reais ficam no proxy do host, no Mac.

E o resíduo não valeria nada

O layout dos blocos é metade da resposta. A outra metade é o que um agente de código escreve no disco, que é o que fez esse bug valer um relatório: arquivos .env, arquivos de credenciais, perfis do Chromium.

Dentro de um workspace Bromure esses arquivos existem, e as ferramentas que os leem funcionam como sempre, mas os valores dentro deles são falsos. Nenhum arquivo, variável de ambiente ou processo dentro da VM jamais guarda uma chave de API real, um token OAuth, um segredo da AWS ou uma chave privada SSH. O convidado recebe, em vez disso, placeholders que preservam a estrutura: sk-ant-api03-brm-…, um token do GitHub com o formato ghp_, brm_…, determinísticos por instalação, para que os validadores do lado do cliente fiquem satisfeitos e nenhuma ferramenta pense que uma chave girou entre sessões. Os valores reais vivem no mapa de substituição em memória do proxy do host, no Mac, cada um associado ao host de destino ao qual pertence, e são trocados na saída com escopo exato-ou-subdomínio, nunca por substring. As requisições AWS são reassinadas no lado do host: o convidado assina com um segredo falso, o host remove essa assinatura e assina de novo. O SSH é somente-assinatura sobre uma ponte vsock, e os bytes das chaves privadas nunca cruzam para dentro do convidado.

O Bromure trata o perfil do Chromium separadamente. O painel de navegador que o agente pilota roda na sua própria VM Alpine efêmera, um clone copy-on-write no diretório temporário que o app exclui quando você desmonta o painel. Manter os logins é opt-in, e isso coloca o perfil num disco criptografado por workspace.

Se alguma falha futura expusesse os blocos de um convidado Bromure, o leitor sairia de lá com uma árvore de diretórios, código-fonte que você já tem, e um arquivo de credenciais que não pode ser gasto em lugar nenhum.

Descartabilidade como verbo

Workspaces são persistentes de propósito. Programar precisa de estado durável: repositórios clonados, caches de pacotes, toolchains, histórico do shell. Então o Bromure controla o que a VM tem permissão de guardar em vez de destruí-la toda noite, e apagar um disco é algo que você pede. O app é explícito sobre cada forma de pedir:

Resetar para a base

Exclui disk.img para que o próximo lançamento re-clone uma cópia fresca da imagem base. A caixa de diálogo nomeia o que se vai: tudo que você instalou com apt install, e suas edições em /etc, /var, /usr, /opt. O home sobrevive.

Apagar o home, excluir o workspace

Apagar o home derruba /home/ubuntu e o par de chaves gerado. Excluir o workspace remove todo o diretório de perfil, as duas imagens, o arquivo de segredos criptografado, cada checkpoint e o material SSH do lado do host, e então libera o endereço MAC reservado.

Descartáveis e checkpoints

bromure-cli vm run --rm lhe dá um workspace no estilo docker, excluído quando a VM para. Todo boot bem-sucedido deixa um checkpoint copy-on-write do disco e do home, tirado somente depois de um boot que produziu um terminal funcionando, então cada um é comprovadamente inicializável.

A limpeza forçada

Se o proxy pega uma credencial placeholder indo para um lugar ao qual ela não pertence, o Bromure marca o workspace como comprometido, e ele não vai inicializar de novo até você limpar seu disco e seu home. Você mantém suas configurações, tokens e chaves SSH, e a limpeza nunca toca em pastas compartilhadas do host.

Esse conjunto de verbos é a diferença entre um apagamento que você pediu e um apagamento que você presumiu.

Pergunte o que o último inquilino deixou

A Cloudflare achou a má configuração, corrigiu-a em nove horas, limpou os pools e os snapshots, construiu assinaturas de detecção para checar o passado, e publicou o tamanho do bloco e o nome da flag. Leia isso como um fornecedor lidando bem com um dia ruim.

“Sandbox” nomeia uma esperança a respeito de uma fronteira, e essa fronteira acabou sendo um ciclo de vida de recurso em vez de um muro. A resposta da indústria a agentes executando código não revisado foi mover a execução para outro lugar e confiar que esse outro lugar chega limpo. Ele chega, na maior parte do tempo. Você não pode verificar isso de dentro do sandbox, nesta semana ou em qualquer outra.

Rode o código numa infraestrutura que você pode ver e você passa a responder à pergunta do resíduo por conta própria: um arquivo de imagem por workspace, blocos livres tirados do seu próprio volume, um objeto compartilhado e ele é uma imagem base pública assinada, e nada naquele disco que valha a leitura se algum dia vazasse. Apagá-lo é um item de menu.