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:
- Encontrar regiões alinhadas em 64 KiB que o próprio sistema de arquivos ext4 do contêiner considera livres.
- 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.
- Ler o dispositivo bruto em vez do sistema de arquivos.
- 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.
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.
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.