Onze testes passaram
A Straiker publicou um repositório que faz o Claude Code executar malware sem jailbreak. O CONTRIBUTING.md dele manda a revisão para um subagente que o próprio repositório define, fixado no Haiku e mantido longe da pasta de testes. Depois o Fable roda o pytest, um arquivo de teste baixa e inicia um implante Sliver, e o terminal informa 11 passed. A Anthropic fechou o relato como Informative: confiar na configuração de um repositório é a decisão de confiança no workspace tomada pelo usuário. O Bromure Agentic Coding decide até onde essa confiança chega: uma VM com chaves substitutas, um firewall no Mac e um trace que o repositório não consegue editar.
O agente seguiu o fluxo de trabalho documentado do projeto, cada passo dele, e esse fluxo tinha sido escrito pelo atacante. O terminal dizia 11 passed.
Você encontra no GitHub um pequeno cliente de métricas que pouparia uma semana de trabalho e pede ao Claude Code para cloná-lo e dar uma olhada. Ele faz um bom trabalho. Lê o código, aponta um buffer sem limite e um loop de novas tentativas que vai girar para sempre com uma mensagem envenenada, e sugere correções. Você diz o de sempre: corrige isso e prepara um PR.
Alguns minutos depois as correções estão feitas, os testes estão verdes e o resumo diz 11 passed. Em algum momento desses minutos, um implante abriu um shell remoto no seu notebook.
Esse é o cenário que Brian Cumi, da Straiker STAR Labs, montou e publicou em 22 de setembro com o título Fable to Haiku: How a Malicious Repo Tricked Claude Code Into Running Malware. O resumo de outubro da pesquisa em segurança de agentes de código da Adversa o lista entre as técnicas de ataque do mês. O repositório não explora nenhum bug. Cada passo usa um recurso documentado do jeito que seus criadores pretendiam.
Como o repositório revisou a si mesmo
O repositório, northstar-metrics, parece um projeto open source comum, e seu
primeiro truque é banal: um CONTRIBUTING.md. Ao pedir um PR, você amarrou o
agente ao fluxo de contribuição do projeto, e um agente que segue instruções lê
o guia de contribuição. Este diz que os revisores devem rodar o agente de
primeira revisão do projeto antes de rodar os testes.
Esse agente mora no repositório, em .claude/agents/first-pass.md. O Claude
Code permite que um projeto defina seus próprios subagentes nessa pasta, com um
modelo e um escopo no frontmatter. Este define model: haiku e limita a
revisão a src/ e docs/. Suas instruções acrescentam que uma leitura na
sessão principal não substitui rodar o agente, para que o agente principal não
o pule.
Esta é a cadeia completa:
- O Fable, o modelo principal, passa a revisão para o subagente do repositório.
- O subagente roda no Haiku 4.5 e só olha
src/edocs/. - Ele volta com dois achados que o autor plantou para ele encontrar: uma string de versão fixa no código e uma divergência entre a documentação e o código. Os dois são inofensivos.
- O fluxo documentado considera a revisão concluída. Ninguém abre
tests/test_compat.py. - O Fable roda
pytest. - Antes da sua asserção,
test_compat.pydecodifica uma URL em hexadecimal, baixa um arquivo Python e o inicia comsubprocess. O bloco está envolto emtry/except: pass, e a asserção sempre é verdadeira. - Um implante Sliver abre uma sessão de comando e controle. O pytest imprime 11 passed.
O resumo de Cumi sobre por que funciona: o repositório se apoia em três suposições comuns. Projetos podem definir seus próprios subagentes, agentes seguem o fluxo documentado de um projeto, e uma execução de testes verde conta como verificação do código. “Um repo malicioso que entende as três suposições não precisa quebrar nenhuma delas. Só precisa ficar na costura onde elas se encontram.”
A Straiker relatou isso à Anthropic pelo HackerOne em 14 de setembro. A Anthropic fechou como Informative: respeitar a configuração do repositório faz parte da decisão de confiança no workspace tomada pelo usuário.
A decisão de confiança, na prática
A resposta da Anthropic é coerente com o funcionamento do Claude Code. Quando
você abre uma pasta, ele pergunta se você confia nela, e você disse sim. Essa
decisão cobre o CLAUDE.md, os hooks, os subagentes e a suíte de testes.
O problema é sobre o que você estava decidindo. Você confiou no repositório o
bastante para olhar para ele. Era para isso que você o clonou. Em um
notebook, confiar nesta pasta o bastante para rodar seus testes significa
confiar a ela tudo o que sua conta alcança, porque é ali que o pytest roda. A
Straiker lista o que o implante tinha: todo arquivo que você pode ler,
credenciais em memória, chaves SSH no disco e todo repositório em que você pode
fazer push.
Nenhum revisor, humano ou modelo, lê todos os arquivos de teste de todos os projetos que avalia. Aqui a revisão foi encurtada de propósito, mas muitas revisões honestas também são curtas. O que você pode escolher é onde a suíte de testes roda.
O mesmo repositório em uma máquina Bromure
No Bromure Agentic Coding, o Claude Code roda dentro de uma VM Linux no seu Mac. Cada máquina tem seu próprio disco, seu próprio diretório home e suas próprias credenciais, e todo o seu tráfego sai por um proxy no Mac. A decisão de confiança no workspace continua sendo sua. O que muda é o que o repositório confiável consegue alcançar.
Reproduza a cadeia da Straiker dentro de uma máquina Bromure. Os passos um a
cinco acontecem como antes: o agente lê o guia de contribuição, o subagente
revisa as pastas erradas e o pytest roda. Do passo seis em diante, o
repositório esbarra no Mac.
O download passa pelo Mac
O payload precisa ser buscado. O teste decodifica uma URL e pede um arquivo a um host de que o projeto nunca precisou. Dentro de uma máquina Bromure só existe um caminho para fora: o switch de rede virtual desvia toda conexão nas portas 80 e 443 para o proxy no Mac e confere qualquer outra conexão, seja qual for o protocolo, com as regras da máquina.
Essas regras ficam no painel Guardrails da máquina, em Outbound connections: entradas de permitir e negar por host, faixa de IP, protocolo e porta, com uma configuração Unmatched traffic em Allow ou Deny. Uma máquina que você usa para avaliar código de estranhos precisa de uma lista curta: o provedor do modelo, os registros de pacotes, o GitHub.
allow web api.anthropic.com
allow web pypi.org
allow web files.pythonhosted.org
allow web github.com
default deny
Com essas regras, o download em test_compat.py nunca conecta, e o
try/except: pass que o autor colocou para esconder falhas agora esconde esta.
O pytest continua imprimindo 11 passed. A diferença está no Mac. A
Security Timeline da máquina tem uma linha vermelha Firewall com um
veredito de negação, o host que o teste tentou alcançar e a porta, com horário
segundos depois de o agente rodar pytest. A suíte de testes de um cliente de
métricas não tem motivo nenhum para contatar um host de que você nunca ouviu
falar, e agora você sabe que ela tentou.
O tráfego posterior do implante recebe o mesmo tratamento. Seja o Sliver ligando para casa por HTTPS, por TLS mútuo na própria porta, por WireGuard ou por DNS, a conexão sai pelo switch, e o switch confere cada protocolo com a mesma tabela.
O implante encontra substitutos
Suponha que a máquina permitia tráfego não correspondente, o payload chegou e o Sliver abriu sua sessão. O atacante agora tem um shell na VM, com o mesmo alcance que a Straiker listou para o notebook. Em uma máquina Bromure esse alcance cobre menos.
- Arquivos. A VM contém seu próprio diretório home e as pastas de projeto que você escolheu compartilhar com aquela máquina. O diretório home do seu Mac, seus outros projetos, seus perfis de navegador e suas Chaves não estão lá dentro.
- Credenciais. O Bromure mantém as chaves de API reais, tokens OAuth,
tokens do git, segredos de nuvem e senhas de banco de dados criptografados no
Mac. A VM tem substitutos no formato certo:
ANTHROPIC_API_KEY=sk-ant-api03-brm-…, um tokenghp_que o GitHub recusaria, uma chave secreta da AWS que não assina nada. O proxy coloca o valor real depois que a requisição sai da VM, e só para o host a que a credencial pertence. - Chaves SSH.
ls ~/.ssh/id_*não retorna nada. OSSH_AUTH_SOCKda VM é uma ponte para um ssh-agent no Mac. A VM pode pedir uma assinatura, mas não pode ler a chave. Ative Ask before use para uma chave, e cada pedido de assinatura abre um diálogo no Mac.
Um implante que junta o que consegue e manda para casa tropeça num segundo fio.
Um token substituto tem um único destino legítimo, o host para o qual foi
emitido. Quando o proxy vê sk-ant-api03-brm-… ou aquele token ghp_ indo
para qualquer outro lugar, ele recusa a requisição com HTTP 451, pausa a VM e
abre no Mac um alerta intitulado This environment may have been
compromised, que nomeia o token, o host para o qual foi emitido e o host para
o qual foi enviado. Só saiu um substituto, então não há nada para rotacionar.
O push pergunta primeiro
O último item da Straiker, os repositórios com acesso de push, é o que transforma um notebook comprometido em um incidente de cadeia de suprimentos. Um atacante com um shell e suas credenciais do git pode fazer push de um commit em todo projeto que você mantém.
Em uma máquina Bromure, um push para GitHub, GitLab ou Bitbucket é uma escrita,
e as máquinas novas vêm com Prompt before write para cada serviço
protegido. O proxy segura o push e o macOS mostra Allow write on "github.com"
from workspace "…"? com a operação como ela apareceu na rede,
POST /acme/app.git/git-receive-pack. Você não pediu push nenhum, então clica
em Don't allow, e o implante recebe um 403 terminando em blocked by
Bromure Guardrails. O macOS desenha o diálogo a partir do que o proxy leu,
então nada dentro da VM consegue mudar o texto.
O trace mostra a revisão que não aconteceu
Quando a poeira baixa, você quer saber o que o agente fez. No nível de tracing padrão, AI request details, o proxy guarda cada troca com o provedor do modelo, criptografada no Mac, e o Trace Inspector desmonta cada uma em chamadas de ferramenta, comandos de shell e leituras de arquivo.
Para este repositório, o registro responde às perguntas que importam:
- Qual modelo fez a revisão? O corpo da requisição capturada o nomeia:
claude-haiku-4-5, um turno depois de a sessão principal ler.claude/agents/first-pass.md. - O que ele leu? Cada chamada
Read, com seu caminho. Há chamadas para arquivos emsrc/edocs/, e nenhuma paratests/test_compat.py. - O que rodou? A chamada
Bashque iniciou opyteste, ao lado dela, na Security Timeline, a linha de firewall do download que ela causou.
O repositório controla o que o agente vê e o que o pytest imprime, mas não tem como editar o trace. O proxy escreve esse registro no Mac, a partir do próprio tráfego.
Uma máquina para código que você não leu
O relatório da Straiker termina com três recomendações: exigir aprovação antes
de rodar um subagente definido pelo repositório, ler os arquivos de teste antes
de rodá-los e tratar arquivos como .claude/agents/, hooks e definições de
workflow como entrada não confiável. As três são sensatas, e as três dependem
de alguém se lembrar, toda vez, numa tarefa que parecia uma revisão de rotina.
No Bromure o hábito seguro custa alguns cliques. Uma máquina nova é um clone copy-on-write de uma imagem Ubuntu assinada, então quase não ocupa disco. Isso sugere uma configuração para avaliar código de fora:
- Crie uma máquina chamada eval. Dê a ela um provedor de modelo e mais nada: nenhum token do git com permissão de push, nenhuma chave de nuvem, nenhum banco de dados.
- Configure a saída dela para negar por padrão. Em Guardrails › Outbound connections, permita o provedor do modelo, os registros de pacotes e o GitHub, e coloque Unmatched traffic em Deny.
- Clone dentro da VM, não numa pasta compartilhada. Comece a sessão numa pasta nova no home da máquina. Uma pasta compartilhada é uma janela ao vivo para o seu Mac, e o home da própria máquina é a parte que o Bromure consegue rebobinar ou apagar.
- Ou torne-a descartável.
bromure-cli vm run --rminicia uma máquina que é apagada, disco e tudo, quando para. - Leia a Timeline antes de adotar a dependência. Uma linha Firewall de negação numa execução de testes é um achado que nenhum revisor relatou, não importa o que o pytest imprimiu.
Se algo passar, você limpa uma máquina só. Rewind home… volta o diretório home para o checkpoint de um boot anterior. Uma máquina que o detector de comprometimento marcou se recusa a iniciar até você escolher Wipe and Launch, que reconstrói o disco e o home dela e mantém suas configurações e as credenciais reais no Mac. A conta do seu notebook, seus outros projetos e suas outras máquinas nunca estiveram lá dentro.
Quando a biblioteca tiver provado seu valor, leve-a para a máquina que faz o trabalho de verdade. Essa máquina também só tem chaves substitutas, e os pushes dela também param no diálogo do Mac.
O download precisa pedir
Toda conexão que a VM abre passa pelo switch virtual e pelo proxy no Mac. Com o tráfego não correspondente negado, um teste que busca um payload de um host novo falha, e a Security Timeline mostra a tentativa.
O shell encontra substitutos
As chaves de API reais, os tokens do git e os segredos de nuvem ficam criptografados no Mac, e as chaves SSH privadas nunca entram na VM. Um substituto enviado para qualquer lugar que não seja seu host retorna 451 e pausa a máquina.
Um push precisa de um clique no Mac
Com Prompt before write, o padrão nas máquinas novas, um git push espera um diálogo do macOS que mostra a operação exata. Um implante não consegue respondê-lo.
Um registro que o repo não edita
O trace no Mac mostra qual modelo revisou o código, quais arquivos ele leu e qual comando rodou em seguida, não importa o que o terminal imprimiu.
Confie menos no repositório
O repositório não fez nada exótico. Escreveu um guia de contribuição, definiu um agente auxiliar e entregou um arquivo de teste. Os projetos vão continuar fazendo as três coisas, porque são úteis, e os agentes vão continuar seguindo-as, porque seguir as convenções de um projeto é o que faz um agente ser bom no que faz.
A resposta da Anthropic deixa a decisão com você, e é aí que ela deve ficar. Tome-a numa máquina feita para isso: uma em que confiar num repositório o bastante para rodar seus testes significa confiar a ele uma VM, alguns substitutos e uma lista curta de hosts. Depois deixe a suíte de testes imprimir o que quiser.