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

O symlink saiu do workspace, e não encontrou nada que valesse levar

O GhostApproval mostrou como um repositório hostil pode enganar seis assistentes de codificação com IA para que escrevam e leiam arquivos fora da pasta em que deveriam ficar: plantando chaves SSH, sequestrando a inicialização do shell e lendo credenciais de nuvem na própria máquina do desenvolvedor. O bug é um erro de resolução de symlink. O motivo de ele doer é que o agente roda em uma máquina cheia dos seus segredos. O Bromure Agentic Coding muda esse segundo fato.

Um agente de codificação é um programa que você convida a ler e escrever arquivos no seu laptop. O GhostApproval é a história do que acontece quando um repositório hostil convence esse programa a escrever um pouco fora das linhas: nas suas chaves SSH, na configuração do seu shell, nas suas credenciais de nuvem. O bug é velho e sem graça. O estrago vem de onde o agente estava parado.

Em 8 de julho de 2026, a equipe de pesquisa da Wiz publicou o GhostApproval, um relato da mesma falha encontrada, em seis formas diferentes, em seis assistentes de codificação com IA populares: Amazon Q Developer, Claude Code da Anthropic, Augment, Cursor, Antigravity do Google e Windsurf. Os detalhes variam por produto. A forma, não. Você abre um repositório, talvez um tutorial que alguém linkou ou um projeto que um colega pediu para você olhar, e aponta seu agente de codificação para ele. Em algum lugar desse repositório há um arquivo que não é um arquivo.

Um arquivo que não é um arquivo.

O truque é um link simbólico, ou symlink: uma entrada do sistema de arquivos que parece um arquivo, mas é uma placa apontando para algum outro caminho. Symlinks são um recurso normal do Unix, com quarenta anos de idade. O problema de segurança, catalogado como CWE-61, é quase tão velho: um programa que abre um symlink sem verificar para onde ele aponta deixa um atacante guiá-lo até um arquivo que o autor nunca pretendeu.

No GhostApproval, um repositório malicioso traz um symlink com nome de aparência inocente, digamos project_settings.json. Em vez de configurações, ele guarda um ponteiro para ~/.ssh/authorized_keys, ou ~/.zshrc, ou ~/.aws/credentials, no diretório home do desenvolvedor, bem fora da pasta do repositório. Um README, ou o usuário agindo segundo as instruções do repo, então pede ao agente para "atualizar o project_settings.json". O agente segue a placa sem resolver aonde ela leva, e escreve no alvo real.

WORKSPACE — o repo clonadoproject_settings.jsonparece um arquivo de config — é um symlink"Por favor atualize project_settings.json"— o README do repo, obedecido pelo agenteFRONTEIRA DO WORKSPACEpresumida segura — a suposição é o bugHOME DO DESENVOLVEDOR (~) — fora do repo~/.ssh/authorized_keys~/.zshrc~/.aws/credentialsacesso real ao host · chaves de nuvem reais · shell rodado a cada logino agente segue o linkCWE-451 — O PROMPT DETURPA O QUE ESTÁ PRESTES A ACONTECERAlvo real do agente: ~/.ssh/authorized_keysMostrado a você: "Editar project_settings.json? [Sim]"
Como o GhostApproval funciona. Um repositório clonado contém um symlink disfarçado de arquivo de configuração; ele aponta para fora do workspace, para o diretório home do desenvolvedor. Quando o agente o edita, a escrita cai em ~/.ssh/authorized_keys, ~/.zshrc ou ~/.aws/credentials. Pior: em vários produtos, o prompt de confirmação mostrava o nome inofensivo que o agente foi mandado editar, não o caminho sensível que ele estava prestes a tocar (CWE-451).

Duas coisas fazem disso mais que uma curiosidade. A primeira é o que os alvos fazem. Acrescentar uma linha a ~/.ssh/authorized_keys instala a chave de um atacante para SSH sem senha na sua máquina. Escrever em ~/.zshrc roda o comando do atacante toda vez que você abre um terminal. O agente da Augment nem precisou escrever: perguntado sobre "chaves AWS no projeto", ele percorreu um symlink até um arquivo de credenciais e imprimiu os segredos no chat, sem prompt algum.

A segunda é uma falha mais sutil, que a Wiz classifica como CWE-451, deturpação da interface do usuário. Com o Claude Code, o próprio raciocínio interno do agente reconheceu o arquivo como uma config de shell, e o diálogo de confirmação ainda assim perguntou apenas "Fazer esta edição em project_settings.json?". O único momento em que um humano poderia ter pegado o perigo mostrou ao humano a informação errada. Windsurf e Amazon Q Developer estavam piores: escreviam o arquivo no disco antes de mostrar os botões de aprovação, então o diálogo era um prompt de desfazer, não um portão. A chave SSH do atacante já estava no lugar.

A maioria dos fornecedores já corrigiu sua variante: o Cursor na v3.0 (CVE-2026-50549), o Amazon Q no language-server 1.69.0 (CVE-2026-12958), o Google no Antigravity v1.19.6, o Claude Code com um aviso de resolução de symlink. Essa é a resposta certa, e você deve aplicar as atualizações. Mas remendar o tratamento de symlinks de cada produto conserta o mecanismo e deixa o que está em jogo onde estava.

O que está em jogo depende de onde o agente está.

Cada um desses payloads assume a mesma coisa: que alcançar o diretório home do desenvolvedor vale o esforço. Que ~/.ssh guarda uma chave que abre uma máquina real, ~/.aws/credentials guarda um token de nuvem vivo, e ~/.zshrc roda em um shell que você vai abrir amanhã. O symlink é a estrada. O destino é um laptop com toda a sua vida de trabalho dentro.

A maioria dos ataques a agentes de codificação repousa sobre a mesma suposição. O agente é um programa capaz e aberto que lê entrada não confiável (repositórios, páginas web, tarballs de pacotes, saída de ferramentas) e executa ações em uma máquina onde suas credenciais ficam em arquivos comuns, a alguns diretórios de distância. Endureça o agente o quanto quiser; ele compartilha um sistema de arquivos e um chaveiro com tudo o mais que você roda. Atacantes vão continuar pavimentando estradas novas. O destino é o problema.

O Bromure Agentic Coding remove o destino. O agente, os shells que ele gera, os repositórios que ele clona e qualquer código dentro deles rodam dentro de uma VM Linux por perfil: uma máquina separada que o hypervisor isola do seu Mac. Suas credenciais reais nunca entram nela. Escrevemos sobre por que uma fronteira vence uma jaula em por que o Bromure Agentic Coding não é um sandbox; o GhostApproval é um teste limpo da ideia, porque ele escapa da pasta do workspace, a única pequena fronteira em que a indústria estava se apoiando.

Agente no seu MacUMA MÁQUINA, UM HOMEagente + repo clonadosymlink → ~/.ssh/authorized_keys~/.ssh/authorized_keysREAL~/.aws/credentialsREAL~/.zshrcREALResultadochave SSH do atacante instalada,chaves de nuvem lidas, shell sequestrado —na máquina em que você trabalha.Bromure Agentic CodingVM POR PERFIL — DESCARTÁVELagente + repo clonadosymlink → ~/.ssh/authorized_keysa escrita ainda acontece~/.ssh/authorized_keysSTUB~/.aws/credentialsSTUB~/.zshrcconvidadoResultadochave adicionada a um convidado descartável,chaves de nuvem placeholder exfiltradas —seu Mac nunca entrou na sala.
O mesmo escape de symlink, duas máquinas. Em uma configuração normal, o agente roda ao lado dos seus ~/.ssh e ~/.aws reais, então seguir o link alcança chaves vivas e uma máquina que vale a pena tomar. No Bromure, o agente roda em uma VM por perfil; o symlink ainda resolve e a escrita ainda acontece, mas cai em um convidado descartável cujo diretório home guarda apenas stubs: arquivos placeholder que não significam nada na internet pública. O sistema de arquivos do seu Mac fica abaixo da linha do hypervisor e nunca é montado na VM.

Trace o mesmo escape dentro da VM. O symlink ainda resolve (o Bromure nunca precisa detectar ou bloquear o truque) e a escrita ainda cai em ~/.ssh/authorized_keys. Mas esse arquivo vive no diretório home do convidado, em uma máquina descartável que não tem rota para o seu Mac. A chave do atacante agora concede login a uma máquina que você vai jogar fora, uma que, para começar, nunca foi alcançável de fora.

A variante de roubo de credenciais é ainda mais anticlimática. Quando um agente comprometido percorre um symlink até ~/.aws/credentials e imprime o conteúdo no chat, ele lê um stub: um arquivo de credenciais bem formado cujas chaves não autenticam em nada. Os tokens reais ficam no host, atrás de um broker que troca o placeholder pelo segredo real apenas no momento de uma chamada de saída que você permitiu, e o troca de volta para fora da resposta. Uma dependência ou um agente enganado que varre o sistema de arquivos em busca de segredos encontra mobília.

O prompt, e a segunda rede.

O Bromure enfrenta as duas arestas mais afiadas do GhostApproval em camadas diferentes.

A primeira é o problema do prompt de confirmação, o CWE-451: o diálogo que dizia project_settings.json enquanto o agente sabia que significava ~/.zshrc. O Bromure dispensa o diálogo de escrita de arquivo; escritas dentro da VM descartável tocam um disco dispensável e não precisam de aprovação. Em vez disso, o Bromure insere o humano na fronteira real, para as chamadas que alteram estado e saem da VM: um git push, um DROP de banco de dados, um Terminate de nuvem. Essas pausam no host e mostram a você a operação literal, não um resumo escrito pelo agente. É o mesmo princípio que o GhostApproval violou, aplicado onde importa: o momento em que uma decisão tem consequências fora do sandbox é o momento em que um humano deve ver a chamada real.

A segunda aresta é a instrução que inicia o ataque inteiro: um README mandando o agente "atualizar o project_settings.json", que o modelo lê como contexto e obedece como ordem. O Bromure lê esse conteúdo não confiável primeiro, no dispositivo: um classificador local pontua arquivos que chegam, buscas na web e saída de ferramentas em busca de instruções que não têm nada que estar ali, e os arquivos de regras que um agente trata como ordens permanentes recebem um exame mais severo. O classificador vai deixar passar algumas formulações, e o isolamento derrota este ataque sozinho; trate-o como uma segunda rede sob a primeira.

Onde a linha não salva você.

O isolamento é uma forma específica, e a forma tem arestas.

Um perfil vive muito, então a persistência persiste.

O payload no ~/.zshrc vai rodar na próxima vez que um shell abrir dentro da VM daquele perfil. Ele roda em um convidado sem chaves do host e só com credenciais stub, um shell em uma sala vazia, mas roda. Se isso preocupa você em uma dada tarefa, rode-a em um perfil novo.

O que você monta, você expõe.

A VM não guarda nada seu por padrão. Se você deliberadamente montar um diretório real do host em um perfil, ou colar um token vivo no convidado, um escape de symlink pode alcançá-lo. A linha mantém seus segredos fora; colocá-los de volta é uma escolha que só você pode fazer.

Delimite o broker de propósito.

Stubs significam que o arquivo no disco não vale nada, mas o broker ainda guarda tokens reais para as chamadas que você permite. Um perfil que só precisa ler um repo não deveria carregar um token capaz de publicá-lo. O isolamento contém a explosão; o escopo decide quão grande ela poderia chegar a ser.

O workspace era a parede errada.

O GhostApproval é, no fundo, uma história sobre o que a indústria concordou em tratar como fronteira: a pasta do workspace, a ideia de que "o agente fica no diretório do projeto" é uma propriedade de segurança, e não um padrão que qualquer placa pode contornar. O symlink em si é um bug que os fornecedores podem corrigir, e corrigiram. Desenhe sua fronteira em uma pasta e você vai passar a eternidade provando que nenhum symlink, nenhum .., nenhum caminho esperto jamais sai dela. Desenhe-a no hypervisor e a pergunta deixa de importar, porque o lugar para o qual o agente escapa é uma máquina descartável que nunca guardou nada seu.

Aplique os patches dos fornecedores; eles fecham um buraco real. Mas o buraco doeu porque o agente estava parado no seu laptop, a um diretório das suas chaves. Tire o agente do seu laptop e o próximo escape (haverá um) chega a uma sala vazia. O Bromure Agentic Coding é gratuito, open-source e já está disponível hoje.