Bastava estar logado
Um desenvolvedor que estava liberando espaço em disco encontrou 700 MB num diretório oculto e puxou o fio: o ZCode, da Z.ai, estava empacotando em tar toda a sua árvore de trabalho, 42.411 arquivos com 86,6% deles em .git, criptografando com uma chave que só a Z.ai tem, e postando direto num bucket da Alibaba Cloud. Nenhum atacante, nenhum CVE, nenhum malware. O gatilho era estar logado. O cliente criptografava o pacote, então nenhum scanner conseguia ler, e os únicos fatos que sobravam eram o destino e o tamanho. Esses são dois dos campos que o Bromure Agentic Coding anota para cada requisição que um workspace faz, num fio que a ferramenta dentro da VM não escolhe.
Sua ferramenta de programação não precisa estar comprometida para mandar seu código-fonte para algum lugar. Basta um padrão de fábrica. Confiar no fornecedor não é a questão. Pergunte se você ficaria sabendo.
Você está liberando espaço num disco cheio. Um diretório oculto na sua pasta
pessoal está ocupando mais de 700 megabytes, e você não lembra de ter instalado
nada desse tamanho. Dentro dele, numa pasta chamada checkpoints, há um único
arquivo de 313 MB com a extensão .enc e, ao lado, um registro de metadados que
diz failureCount: 564.
Quinhentas e sessenta e quatro tentativas de algo que você nunca pediu.
Setecentos megabytes e um fio para puxar
Foi aí que o desenvolvedor chinês conhecido como ferstar começou, em 18 de setembro, e o relato de engenharia reversa do que ele encontrou vale ser lido por inteiro. O diretório pertencia ao ZCode, o cliente de programação com IA para desktop da Z.ai, a empresa por trás dos modelos GLM. CSO Online e InfoWorld trazem os dois o resumo dele do comportamento, palavra por palavra:
“Sempre que você está logado, o ZCode empacota silenciosamente todo o seu workspace — histórico .git completo, cache de assets LFS, reflogs e configurações globais do aplicativo —, criptografa, e envia direto para o Aliyun OSS.”
O gatilho está na primeira oração. Não uma pergunta que você fez, não o recurso de indexação que você ligou: um token de sessão válido, que o processo de captura espera, e nada mais. Uma única sessão registrou 62 eventos de captura nos logs dele. Quando ele apagou o pacote, o cliente construiu outro em menos de meia hora e incrementou o contador de falhas.
O que havia na caixa
O snapshot que ele desmontou continha 42.411 arquivos, 345 MB antes da criptografia e 313 MB depois. O que compunha esses 345 MB:
.git/lfs/: 196,1 MB, 56,8%.git/objects/: 102,2 MB, 29,6%.git/logs/: 0,6 MB, 0,2%- todo o resto, ou seja, código e documentação de verdade: 46,2 MB, 13,4%
86,6% do upload era .git. O pacote levava cada objeto do histórico do
repositório, cada binário grande que o cache LFS tinha baixado, e o reflog, que
lista cada branch em que você esteve e cada commit que você fez e depois desfez.
Quem lê sua árvore de trabalho descobre o que você está entregando; quem lê seu
.git descobre o que você tentou, o que você reverteu, e quem fez o commit.
As duas chaves governavam o lado deles
O ZCode vinha com duas configurações que um desenvolvedor cuidadoso teria achado e desligado: uma para otimização da experiência, outra para indexação de snapshots do repositório. As duas faziam algo real, e nenhuma fazia o que o nome sugere.
A primeira governava se a Z.ai estava autorizada a usar os dados. A segunda governava se o servidor indexava o snapshot depois de ele chegar. O empacotamento e o upload rodavam de todo jeito, porque o sidecar de captura sobe assim que o provedor de tokens lhe entrega um JWT válido, sem nenhuma preferência do usuário em lugar nenhum do caminho. Desligue as duas chaves e o cliente ainda empacotava seu repositório, ainda criptografava, ainda postava no bucket, e prometia discrição sobre o que acontecia depois disso.
O bug de um fornecedor, mas o formato se repete em todo lugar. Um controle que mora dentro do programa que ele restringe é um pedido. Ele roda no mesmo código que precisaria estar errado para você precisar dele.
Nada no fio conseguia ler aquilo
O cliente criptografava cada pacote com AES-256-CTR antes de ele sair da máquina e
envelopava a chave simétrica com uma chave pública RSA-OAEP-SHA256 entregue a ele
pelo zcode.z.ai. A chave privada correspondente nunca desce. ferstar não
conseguia abrir o próprio pacote; a única parte que conseguia era a que tinha o
bucket.
Isso descarta toda defesa sua que funcione lendo bytes. Um scanner de segredos não acha chave alguma da AWS naquele tarball, e a inspeção profunda de pacotes vê uma sessão TLS para um endpoint de armazenamento carregando texto cifrado envelopado em texto cifrado.
Dois fatos sobrevivem: para onde foi e que tamanho tinha. Um POST de 313 MB para um host de armazenamento de objetos numa região onde seu time não faz deploy chama atenção, desde que alguém anote onde suas ferramentas se conectam e quanto elas enviam. Ninguém anotava, e é por isso que achar isso exigiu arqueologia de espaço em disco e uma semana de engenharia reversa em vez de uma olhada num log.
Onde fica a saída
O Bromure Agentic Coding coloca o agente e sua cadeia de ferramentas dentro de uma VM Ubuntu no seu Mac, e dá a essa VM exatamente um caminho para alcançar a internet: um proxy man-in-the-middle rodando no host, fora do guest, onde nenhum processo da VM pode reconfigurá-lo.
Isso não pede nada ao software. O switch de rede virtual desvia de forma
transparente os fluxos da VM nas portas 80 e 443 para o proxy do host, sem
nenhuma variável de ambiente necessária e sem nada que o guest possa desfazer,
ligado por padrão em todo workspace. Uma ferramenta que ignora HTTPS_PROXY,
abre seu próprio socket e fala direto com um endpoint de armazenamento ainda
passa por ali, porque a interceptação mora no switch em que o guest está plugado,
e não no guest.
Aí quatro coisas acontecem com um POST de 313 MB que ninguém pediu.
Toda requisição deixa registro
Toda requisição pelo proxy deixa um registro de trace: timestamp, host, porta, método, caminho, status, latência, e bytes de requisição medidos antes de qualquer troca de credencial. Você não precisa descriptografar um pacote para notar um upload do tamanho de um repositório para um host onde você nunca fez deploy: o tamanho e o destino são colunas. Ordene por uma, filtre pela outra.
Nunca sai do Mac
O firewall de egresso por workspace é uma tabela de regras ordenada (ação,
protocolo, host ou CIDR, portas, métodos HTTP) terminando em
default allow ou default deny. Rode um workspace no estilo lista de
permissões e o upload não tem para onde ir. Dois lugares aplicam as regras:
o switch compara o IP de destino e o hostname captado do DNS, o proxy compara
o nome de servidor TLS de novo, e suas edições chegam às sessões em execução
na hora.
Todo veredito é uma linha
Toda decisão do firewall cai na Security Timeline, então uma ferramenta que fica tentando de novo, 564 vezes digamos, aparece como uma negação repetida contra um host nomeado em vez de um disco enchendo devagar. O padrão que levou uma semana para reconstruir é justamente o que aquela janela existe para mostrar.
As configs que ele empacota são iscas
“Configurações globais do aplicativo” é a expressão que deveria preocupar
você mais na frase de ferstar. Dentro de um workspace Bromure,
~/.git-credentials, ~/.docker/config.json, ~/.kube/config e
~/.aws/config existem e funcionam, e cada valor dentro deles é um falso que
preserva a estrutura. Nenhum arquivo, variável de ambiente ou processo da VM
jamais guarda uma chave de API, um token OAuth, um segredo da AWS ou uma
chave privada SSH de verdade.
O escopo faz o resto. Um workspace é um projeto, ou uma fronteira de credenciais;
dois workspaces nunca compartilham uma VM, e a pasta pessoal do guest é um
arquivo de imagem próprio. O que uma ferramenta empacota dentro de um workspace é
aquele workspace, o que deixa de fora os outros quarenta repositórios do seu Mac,
o ~/.aws/config do seu host, e suas chaves SSH. As chaves nunca chegam a um
guest: a assinatura acontece no host por uma ponte vsock, e os bytes privados
ficam lá.
O que a Z.ai fez depois
A Z.ai se desculpou no dia em que ferstar publicou. A empresa desativou o fluxo de upload no ZCode v3.14.0, que a InfoWorld informa ter saído em 19 de setembro listando uma correção para um problema anormal de upload de repositório, e chamou a firma de segurança NSFOCUS para auditar o resultado. A NSFOCUS confirmou que “todos os objetos de dados no bucket zcode-prod do Alibaba Cloud OSS, assim como o próprio bucket, foram apagados”, e a Z.ai diz que depois da auditoria “nenhum caminho funcional capaz de disparar a geração de snapshots locais de repositório ou de transmitir arquivos locais para fora foi identificado”, e que o código “nunca foi usado para treinamento de modelos”. Depois eles abriram o código: cliente, backend, UI compartilhada, Agent CLI e runtime, sob Apache-2.0, no GitHub. Isso é melhor que a norma do setor, e significa que a próxima pessoa que quiser verificar não vai precisar de um desassemblador.
Cris Thomas, da Semgrep, citado na cobertura da CSO, nomeou a categoria: “Isso não é realmente um problema de modelo de IA, é um problema antiquado de arquitetura de segurança.” Ele quer que os fornecedores divulguem o que sai da máquina, para onde vai e por quanto tempo fica guardado, “com as permissões mínimas ligadas por padrão, não as máximas”. Nenhum cliente consegue impor isso de fora.
O padrão de fábrica é o modelo de ameaça
Ninguém atacou ninguém aqui. Ninguém roubou uma credencial, envenenou um pacote ou convenceu um modelo de nada. Um produto fez o que seus autores construíram para ele fazer, e o que eles construíram para ele fazer foi mandar seu repositório para algum lugar onde você não conseguia lê-lo. O incidente é um padrão de fábrica, e quem escolhe os padrões são pessoas cujos interesses correm ao lado dos seus.
É esse o argumento para colocar a fronteira fora da ferramenta. Você não precisa acreditar que seu assistente de programação é hostil; você só precisa notar que não consegue auditar o que não vê, e que o painel de configurações do fornecedor é um lugar estranho para guardar a chave. Rode o agente e sua cadeia de ferramentas onde cada conexão que eles abrem cruza um fio que é seu, onde o registro dessa travessia é escrito no seu Mac, e onde a regra que barra um upload de 313 MB é uma linha que você digitou em vez de uma caixinha que outra pessoa respeitou.
Aí “o que a minha ferramenta de programação está mandando?” é uma janela que você abre numa terça-feira à tarde, não um projeto de pesquisa.