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

Uma sessão, cem repositórios

O relatório AI Risk and Resilience 2026 da Mandiant descreve um atacante que tomou conta de uma sessão de assistente de código em curso num fornecedor de SaaS, conseguiu instalar por ela um pacote PyPI envenenado, roubou tokens OAuth do GitHub e espalhou o worm Shai-Hulud por cerca de 100 repositórios internos. O relatório não diz como a sessão foi tomada. Num workspace Bromure Agentic Coding não precisa de dizer: o token dessa sessão é um chamariz `ghp_`, a tentativa de o enviar para fora põe a VM em pausa, e os pushes que transformam uma máquina em cem repositórios param no proxy do anfitrião.

Alguém tomou conta de uma sessão de código que estava a correr, autenticada e de confiança, na própria máquina de um programador. Tudo o que se seguiu, até cerca de cem repositórios, saiu do que essa máquina tinha à mão nesse momento.

Um programador de uma empresa de software como serviço perguntou ao seu assistente de código que pacote usar para um problema banal. O assistente nomeou um. O programador disse que sim, que é precisamente para isso que se tem um assistente, e o pip install fez o resto.

A essa altura já um atacante tinha a sessão, e foi ele que escreveu aquela recomendação.

A Mandiant descreve o caso no seu AI Risk and Resilience Report 2026, publicado este mês pelo braço de resposta a incidentes da Google Cloud; The Hacker News noticiou-o a 16 de setembro. O atacante sequestrou uma sessão de assistente de código ativa no posto de trabalho de um programador. Mais cinco passos levaram-no a cerca de cem repositórios de código internos e ao espaço de nomes de pacotes da própria empresa, onde um segundo funcionário puxou a versão envenenada.

A cadeia, salto a salto

Um posto de trabalho, seis passoso que o passo gasta1um atacante toma uma sessão de assistente ativamétodo não divulgado no estudo de caso públicoa sessão2o assistente recomenda software envenenadoo programador aceita, tal como foi desenhadoo sim do programador3um pacote PyPI comprometido instala um ladrão de dadostráfego de dependências banal, instalação banalconfiança do registo4os tokens OAuth do GitHub são lidos do postoo pivô: tudo o que vem a seguir depende distotokens guardados5o Shai-Hulud espalha-se por cerca de 100 repositóriossegredos dos repositórios e código-fonte levadosacesso de escrita6um pacote no espaço de nomes da empresa é envenenadoum segundo funcionário puxa essa versão e é infetadodireitos de publicação
Os seis passos que a Mandiant descreve, e a credencial que cada um gasta. A Mandiant não revela método algum para o passo 1. Em cada passo seguinte, o atacante usa uma autorização que a máquina do programador já tinha: a confiança da própria sessão, depois a do gestor de pacotes, depois a do GitHub, depois os direitos de publicação da empresa.

A Mandiant guarda para si justamente a parte que mais gostaria de saber. O estudo de caso público não diz quando aconteceu a intrusão, e não diz como é que o atacante ganhou controlo de uma sessão em curso. Cookies de navegador roubados, uma extensão maliciosa, uma CLI comprometida numa máquina partilhada: o relatório não escolhe nenhum deles. Quem o ler à procura do remédio sai de mãos vazias.

Os passos 2 a 6 não contêm exploit nenhum. Uma instalação de pacote funciona como as instalações de pacotes funcionam, e código a correr como o programador lê os tokens que estão pousados no disco do programador. Um git push que leva um token válido faz push, e uma publicação que leva direitos de publicação válidos publica. Um passo foi uma intrusão; os outros cinco foram gastar.

O multiplicador são as credenciais, e não para de crescer

O Shai-Hulud é o worm auto-replicante da cadeia de fornecimento que anda a comer contas de mantenedores desde 2025, e aparece como carga útil numa história como esta porque o passo 5 é exatamente aquilo para que os seus autores o construíram. A GitGuardian desmontou uma variante recente em agosto e encontrou-a a varrer 469 localizações de credenciais, contra 189 em versões anteriores: ambientes de desenvolvimento, ferramentas de CI/CD, configuração de cloud, configurações de ferramentas de IA, ficheiros de configuração de gestores de pacotes, histórico da shell, ficheiros .env, definições de IDE, caches de CLI. O worm lê um arquivo em vez de caçar uma falha.

A análise da GitGuardian resume a categoria numa frase:

“Os atacantes deixaram de tentar quebrar as relações de confiança e começaram a usar as credenciais que já fazem essas relações funcionar.”

Um posto de trabalho tornou-se cem repositórios porque tinha à mão autorização para cem repositórios. Aquele número saiu de um inventário, e é o leitor que decide o que entra no inventário.

A sessão só tinha chamarizes

O Bromure Agentic Coding corre o agente dentro de uma máquina virtual Linux no seu Mac, e essa máquina não tem segredos reais. Não tem de ligar nada para isto acontecer. É assim que um workspace guarda uma credencial, ponto final.

O seu token real do GitHub fica cifrado no anfitrião. A VM recebe um falso que preserva a estrutura: ghp_ seguido de 36 caracteres, 40 no total, para que as verificações de prefixo e comprimento do próprio gh passem sem queixas. O Bromure exporta-o como GH_TOKEN e escreve-o em ~/.git-credentials e na configuração do gh, e é a única credencial do GitHub em toda aquela máquina. Um proxy no anfitrião substitui o valor real já no fio, depois de o pedido ter saído da VM, e apenas quando o destino corresponde ao anfitrião para o qual a credencial foi cunhada. Cada falso é determinístico, derivado do valor real mais um sal de 32 bytes próprio da instalação através de HKDF-SHA256, para que um cliente que tire a impressão digital da sua própria chave não veja rotação aparente entre sessões.

Corra o passo 4 contra isso. O ladrão de informação chega, varre as 469 localizações, encontra a variável de ambiente, encontra ~/.git-credentials, encontra a configuração do gh, e despacha o que encontrou. Cada leitura resulta, e cada ficheiro está onde o worm esperava. O atacante acaba com uma cadeia de quarenta caracteres que não autentica em lado nenhum, exceto se voltar a passar por um Mac muito específico.

Os passos 5 e 6 precisam ambos de que o passo 4 tenha produzido um token funcional, e o passo 4 produziu um chamariz.

O roubo é também o alarme

Um token falso tem um único destino legítimo. Nenhum pedido inocente leva um chamariz ghp_ cunhado para github.com até outro anfitrião, por isso o proxy examina cada pedido de saída, cabeçalhos e corpo, à procura de qualquer falso a sair do seu próprio âmbito. Corre um autómato de Aho-Corasick, barato o bastante para ser apontado a todo o tráfego.

Quando o proxy encontra um, faz mais do que registar:

  • recusa o pedido com HTTP 451, e nem um byte dele chega ao destino;
  • o Bromure põe a VM em pausa ali mesmo;
  • um alerta oferece-lhe Desligar, Guardar para investigação (exportar primeiro o disco, a pasta pessoal e as pastas partilhadas para análise forense) ou Continuar por sua conta e risco;
  • a Security Timeline ganha uma linha vermelha Credential brokering;
  • o Bromure marca o workspace como comprometido, pelo que o próximo arranque limpa a imagem de disco e a pasta pessoal persistente. Os seus tokens, chaves SSH e definições do workspace sobrevivem a essa limpeza.

Não ativa nada disto; o detetor está sempre ligado. É a tentativa de exfiltração do passo 4 que trava a sessão, por isso o incidente acaba numa VM em pausa e numa entrada da timeline, em vez de cem repositórios e um estudo de caso.

Cem repositórios são cem pushes

Suponha que quer o cinto além dos suspensórios. O passo 5 é um worm a escrever em repositórios, e o anfitrião classifica as escritas.

As proteções de um workspace trazem uma política de escrita por serviço. Para o GitHub, essa política lê git-over-HTTPS tal como lê a API REST: um git push chega como git-receive-pack e conta como escrita, enquanto um git fetch chega como git-upload-pack e conta como leitura. Apenas leitura recusa os pushes. Perguntar antes de escrever, o predefinido para workspaces novos, retém cada um deles para uma caixa de diálogo do lado do anfitrião que nomeia a operação. Os fetches passam em ambos os casos, por isso o agente continua a trabalhar.

O proxy toma essa decisão no anfitrião, fora da VM. Um agente cuja sessão pertence a outra pessoa não consegue desligar a política nem contorná-la, porque a política não corre do lado do agente da linha. Um worm que queira cem repositórios recolhe cem recusas, ou pergunta-lhe cem vezes.

E o pacote ainda tinha de chegar

O passo 3 é onde a Mandiant dá conselhos concretos: validar as dependências de terceiros recomendadas pela IA contra somas de verificação criptográficas e listas de permissões, e encaminhar o tráfego de dependências por repositórios que controle.

Num workspace, cada obtenção de pacote já passa pelo anfitrião, uma vez que o proxy é a única rota da VM para a rede. O Bromure interceta o PyPI em pypi.org e files.pythonhosted.org e aplica ali a política do anfitrião. O pip.conf dentro da VM pode apertar o que o proxy serviu; não o pode alargar. São três camadas a falar pelos pacotes de Python:

  • a barreira de idade, ligada por predefinição com um mínimo de dois dias, que recusa versões mais novas do que o corte, partindo do princípio de que um lançamento acabado de publicar é o que tem mais probabilidade de ter sido sequestrado agora mesmo. Os índices PEP 503 não trazem carimbos temporais, por isso o Bromure vai procurar a hora de publicação a pedido;
  • a verificação OSV contra api.osv.dev, gratuita e sem chave, que bloqueia qualquer versão que traga um aviso de segurança na gravidade que escolher ou acima dela;
  • o filtro de pacotes comprometidos da socket.dev, que dispara com malware, malware conhecido, typosquatting e scripts de instalação maliciosos.

Um bloqueio volta como um HTTP 451 cujo corpo começa por Bromure Supply-Chain Security blocked this request: seguido do motivo, e o pip imprime isso tal e qual. Vê porque é que a instalação falhou, e o agente também vê, e muitas vezes consegue fixar sozinho uma versão mais antiga. Essa versão mais antiga é aquela para a qual a barreira de idade o estava a encaminhar.

Um posto de trabalho comumpasso 3: o pacote instala-seo registo serve a versão envenenadanada no caminho julga a proveniênciapasso 4: os tokens são lidostokens OAuth reais: env, config, históricoa exfiltração leva uma credencial que servepasso 5: o worm faz pushum token válido faz push, por isso faz pushcerca de 100 repositórios, e o espaço de nomesDentro de um workspace Bromurepasso 3: o pedido é julgado antesidade, OSV, socket.dev, no anfitrião451 antes de um byte chegar à VMpasso 4: a mesma leitura resultaas 469 localizações têm chamarizes ghp_enviar um: 451, e a VM entra em pausapasso 5: o push é classificadogit-receive-pack recusado ou perguntadogit fetch passa; o trabalho continua
Os mesmos três passos de carga, em duas máquinas. À esquerda, um posto de trabalho comum: tokens reais no disco, um roubo que resulta e pushes que passam. À direita, um workspace Bromure Agentic Coding: o anfitrião recusa a versão envenenada antes de ela chegar à VM, o mesmo roubo devolve um chamariz e põe a máquina em pausa, e o anfitrião classifica os pushes, onde uma sessão sequestrada não tem voto.

Os três controlos da Mandiant, e onde eles vivem

O relatório fecha o estudo de caso com três recomendações. Cada uma nomeia um sítio onde um workspace Bromure já põe a fronteira.

Validar as dependências recomendadas pela IA

A Mandiant pede somas de verificação e listas de permissões para tudo o que o assistente sugerir. O seu workspace aplica a política à resposta do registo antes de a VM a ver. O proxy retira da listagem de versões as que são demasiado recentes, pelo que do ponto de vista do agente ainda não existem, e recusa uma versão demasiado recente fixada à mão citando no erro a idade real do pacote. As chaves dos serviços de reputação ficam no anfitrião, fora da máquina que corre a instalação.

Manter longe as chaves em bruto e os tokens de longa duração

É a fronteira do fio escrita como conselho. Nenhum ficheiro, variável de ambiente ou processo dentro da VM tem uma chave de API, token OAuth, segredo da AWS ou chave privada SSH reais. O Bromure guarda os tokens OAuth da subscrição cifrados no anfitrião e renova-os lá cerca de cinco minutos antes de expirarem, pelo que o convidado não leva token de renovação nenhum. O canal entre os dois corre num só sentido: o anfitrião escreve falsos para dentro, e a VM não tem chamada nenhuma que possa fazer para pedir de volta um token real.

Encaminhar o tráfego de dependências por algo que controle

O proxy é a única rota da VM para fora. Cada pedido HTTPS viaja por um socket virtual até ao anfitrião, que termina o TLS, inspeciona o pedido e o reemite através da própria pilha TLS do macOS. É esse caminho único que torna os outros dois controlos executórios em vez de meramente indicativos. Nenhuma obtenção de pacote pode saltar as verificações, e nenhuma credencial pode sair sem ser substituída.

E encolher o raio da explosão de propósito

Cem repositórios caíram porque uma máquina levava autorização para cem repositórios. Corte os workspaces um por projeto, ou um por fronteira de credenciais. Cada um corresponde a uma única VM, não há dois a partilhar uma, e cada um leva as credenciais de que o trabalho desse workspace precisa. O inventário de qualquer máquina define-se num painel de definições.

A parte que não se corrige

A Mandiant enquadra a categoria numa frase que descreve a forma em vez do incidente: “uma fonte de dados envenenada, uma dependência de modelo ou um gancho de extensão podem transformar um agente de confiança num canal para reconhecimento interno, movimento lateral ou fuga autónoma de uma sandbox.”

Três pontos de entrada diferentes, uma frase, e ela vale para os três. O ponto de entrada determina como é que um atacante chega; o conteúdo da máquina de um programador determina até onde ele vai. Este atacante entrou por uma sessão sequestrada. O próximo entra por outra coisa, e o relatório não nomeia porta nenhuma para o leitor trancar.

Construa para o passo a seguir à entrada. Parta do princípio de que a sessão já pertence a outra pessoa, que é o princípio que o estudo de caso lhe impõe ao nunca explicar o passo 1, e pergunte depois o que é que essa sessão alcança.

Uma versão deste incidente acaba no passo 3, com um 451 no terminal e uma linha no Security Log. Outra acaba no passo 4, com uma VM em pausa e uma linha vermelha na timeline a registar que uma credencial tentou sair. Nenhuma das duas chega ao passo 5, e era no passo 5 que estavam os cem repositórios.

Ponha o agente numa máquina que só tenha chamarizes. Instale o Bromure Agentic Coding, e deixe que alguém tome a sessão.