O token estava no mesmo heap
Oren Yomtov, da Accomplish, escapou duas vezes da sandbox do Codex, da OpenAI. Uma das fugas partiu de read-only, o modo mais restrito: JavaScript não confiável chamou v8.getHeapSnapshot() e leu o token de autorização do contexto confiável no heap que os dois compartilhavam. A outra fez o apply_patch ampliar sua própria permissão de escrita até a raiz do sistema de arquivos e depois acrescentou uma linha ao .zshrc. A OpenAI corrigiu as duas em oito dias. A propriedade que as produziu não tem correção: um guarda e o código que ele guarda eram duas variáveis em um único processo. O Bromure Agentic Coding roda o guarda no seu Mac e o agente em uma VM.
O contexto não confiável nunca quebrou a sandbox. Ele pediu ao Node um instantâneo do heap, encontrou ali o token de autorização da própria sandbox como uma string comum, e o usou.
Você aponta o Codex para um repositório que alguém lhe enviou. Você é cuidadoso,
então o roda em read-only, o modo em que o agente pode olhar e não pode tocar.
O agente lê o código. Parte desse código é uma carga maliciosa, e poucos
segundos depois um comando está rodando no seu Mac, fora da sandbox, sem nenhum
aviso na tela.
Isso é o Heapjack, uma das duas fugas da sandbox do Codex que Oren Yomtov, da Accomplish, publicou neste mês. O BleepingComputer as noticiou em 20 de setembro. Yomtov reportou as duas à OpenAI em 12 de agosto, e a OpenAI corrigiu em oito dias: o Heapjack no Codex Desktop build 26.818.21641, a outra no Codex CLI 0.149.0. A declaração da OpenAI é do tipo curto: “Agradecemos aos pesquisadores por nos contatarem e compartilharem suas descobertas. Tratamos das duas questões em agosto, mas estamos continuamente fortalecendo nossas sandboxes.”
Por que o primeiro bug existia é a parte que sobrevive à correção.
Um processo, um heap, um token
O Codex Desktop tem uma ferramenta chamada node_repl que executa JavaScript.
Ela mantém dois contextos dentro de si: um confiável, que pode pedir ao processo
pai que faça coisas reais, e um não confiável, que roda aquilo que o agente está
avaliando. A separação entre os dois é a sandbox.
Ambos os contextos vivem no mesmo processo Node. Nas palavras de Yomtov: “Um processo Node, um isolate V8, um heap compartilhado pelos dois contextos. O token é uma string nesse heap.”
O token é como o processo pai distingue um do outro. Uma requisição que chega pelo pipe com o token certo é confiável e é executada; uma sem ele, não. É um UUID aleatório, que é um ótimo segredo enquanto o código do qual você o esconde não puder ler sua memória.
Ele pode. v8.getHeapSnapshot() é uma API pública do Node, uma ferramenta de
profiling, e devolve um despejo de todo objeto do heap, strings inclusive. O
contexto não confiável a chama, filtra o que tem forma de UUID e termina com um
punhado de candidatos. Isolar o verdadeiro exige mais um passo: testar cada um
contra o pai e observar o que volta. Um token errado responde “não autorizado”.
O certo responde com uma reclamação sobre o resto da requisição. Yomtov fica com
a string que produz a segunda resposta.
Com o token em mãos, o contexto não confiável escreve requisições no mesmo pipe
stdout que o confiável usa. O pai confere o token, acha-o válido, e roda o
comando fora da sandbox. Daí em diante: comandos de sistema via open, o socket
do Docker, edições em arquivos de configuração.
Tudo isso a partir de read-only. Citando Yomtov de novo: “Tudo rodou em
read-only, o modo de sandbox mais restrito, onde o agente supostamente não
consegue escrever nada.”
Uma permissão de escrita que cresce
O Overpatch, a segunda fuga, é mais simples. O Codex CLI tem uma ferramenta
apply_patch para editar arquivos, e ela deduz quanto acesso de escrita precisa
a partir dos caminhos citados no patch que recebeu. Os caminhos vêm do modelo, e
o modelo está lendo o repositório.
Então um patch cita /tmp em uma entrada, com uma alteração que não faz nada. A
alteração falha. A concessão de permissão que ela implicava, não: o acesso de
escrita se amplia até a raiz do sistema de arquivos. Uma segunda entrada do
mesmo patch então escreve através de um link simbólico dentro de $HOME e
acrescenta uma linha ao .zshrc.
Nada escapou ainda. A fuga acontece depois, quando você abre um terminal e seu
shell roda aquela linha como você, sem sandbox alguma no caminho. Esta rodou em
workspace-write, o modo do dia a dia.
Dois bugs diferentes, dois produtos diferentes, uma frase do artigo que cobre os dois: “A coisa que fazia a imposição estava sentada dentro da coisa que estava sendo imposta.”
O guarda e o guardado, no mesmo espaço de endereçamento
Uma sandbox que vive no mesmo processo que o código que ela contém é uma convenção. Ela se sustenta até alguém olhar com atenção. O token está no heap. O cálculo de permissão lê caminhos fornecidos pelo atacante. O pipe é o mesmo pipe. Cada um desses pontos é corrigível, e a OpenAI corrigiu cada um em oito dias. O próximo bug desse tipo será outra string no mesmo heap.
A conclusão do próprio Yomtov é a arquitetural: “É por isso que a Accomplish roda o agente inteiro dentro de uma VM… Tratamos tudo o que está no convidado como não confiável, root incluído, porque nada que decide o que é permitido está rodando lá dentro com ele.”
Esse é o design que o Bromure Agentic Coding entrega. O agente roda em um convidado Ubuntu 24.04 sob o Virtualization.framework da Apple, o hipervisor que o próprio macOS fornece. O convidado é não confiável por construção, root incluído. O código que decide o que é permitido roda como um processo do host do outro lado de um vsock, e nenhum token dentro do convidado autoriza coisa alguma, porque tudo que vale a pena autorizar está fora do convidado.
O que cada fuga encontra no lugar
O Overpatch é o caso concreto, então comece por ele. O truque termina
acrescentando texto ao .zshrc em $HOME. Em um workspace Bromure, o $HOME
do convidado é home.img, uma imagem ext4 anexada àquela VM. O diretório
pessoal do seu Mac não está dentro dele. O que o convidado consegue alcançar no
seu Mac são as pastas de projeto que você compartilhou com ele: até oito,
escolhidas por você, listadas no editor de workspace. Os dotfiles que seu shell
lê quando você abre o Terminal não estão entre elas, a menos que você os coloque
ali.
O Heapjack é o caso mais difícil. Nada no Bromure detecta um truque de instantâneo de heap dentro do convidado, e nada deveria. O que muda é onde a fuga chega. No Codex, o código escapado caía na conta que guarda suas chaves SSH e suas credenciais de nuvem. Em um workspace Bromure, ele cai em uma VM Linux onde:
As credenciais são substitutas
sk-ant-api03-brm-…, uma string com forma de ghp_, um perfil ~/.aws
cujo segredo é inválido. Elas preservam a estrutura, então as ferramentas as
aceitam, e não valem nada lá na frente. Os valores reais vivem na memória do
proxy no host e são trocados na saída, restritos ao host exato ou a seu
subdomínio.
Levar uma para fora dispara o alarme
O proxy examina cabeçalhos e corpos de toda requisição de saída em busca das substitutas do próprio workspace. Uma substituta indo para onde não foi cunhada é bloqueada com HTTP 451, sem um byte encaminhado. A VM pausa, e uma linha vermelha Intermediação de credenciais aparece na Linha do Tempo de Segurança.
A rede é uma lista que você escreveu
A saída é um conjunto ordenado de regras que termina em default allow ou
default deny. O switch virtual desvia os fluxos das portas 80 e 443 da VM
para o proxy no host, sem nenhuma variável de ambiente envolvida e sem nada
que o convidado consiga desligar. As edições se aplicam imediatamente a uma
sessão em andamento.
Um push é uma pergunta, feita no Mac
As credenciais de forja Git carregam uma política de escrita, e novos
workspaces usam por padrão Perguntar antes de escrever. git push é uma
escrita. A caixa de diálogo nomeia a operação exata e é renderizada na sua
área de trabalho. Um atacante dentro da VM não consegue clicar nela.
Quando dá errado, você se recupera com um item de menu. O fluxo de comprometimento apaga o disco e o home do workspace e mantém suas configurações, tokens e chaves SSH. As credenciais reais nunca saíram do seu Mac, então você não rotaciona nada. Redefinir disco re-clona a imagem do sistema. Nenhuma ação do Bromure toca suas pastas de projeto compartilhadas, e o aviso diz isso.
O modo mais restrito foi o que perdeu
O desenvolvedor desta história fez tudo certo. Escolheu a configuração mais
restrita que a ferramenta oferece, de propósito, porque o repositório não era
dele. read-only é um recurso real e fez um trabalho real, até que uma string
em um heap compartilhado a transformou em sugestão.
Modos, listas de permissão e avisos de aprovação valem a pena, e cada um deles presume que o código que os impõe está fora do alcance do código que ele restringe. Isso é uma afirmação sobre layout de memória, e você pode ir verificar. Yomtov verificou o Codex duas vezes, em dois produtos, e a resposta voltou negativa nas duas.
Então rode o agente em algum lugar onde a resposta deixe de importar. Dê root a ele, deixe que instale o que quiser, deixe que leia o repositório que alguém lhe enviou, e mantenha cada credencial, cada decisão de destino e cada confirmação de push do outro lado de um hipervisor, onde a próxima fuga chega em uma máquina que você já estava disposto a jogar fora.