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

O repositório era mesmo da Microsoft

Entre 5 e 6 de junho de 2026, o worm Miasma empurrou código de roubo de credenciais para 73 repositórios em quatro das próprias organizações GitHub da Microsoft — Azure, Azure-Samples, microsoft, MicrosoftDocs — incluindo Azure/functions-action, a Action oficial de deploy, e durabletask, um repositório que já havia sido limpo uma vez em maio. Desta vez o payload não esperou por um npm install. Ele disparou no instante em que um desenvolvedor abriu o repositório no Claude Code, Cursor, Gemini CLI ou VS Code. Eis por que o sinal de confiança — «é um repositório da Microsoft» — foi de novo a superfície de ataque, e o que muda quando o agente que o abre vive em uma VM Bromure por perfil, atrás de um intermediário de credenciais, de um guardrail de leitura/escrita e de um cooldown de pacotes.

Há uma semana o worm entrou montado no escopo npm da Red Hat e disparou através de um hook preinstall. Esta semana ele entrou montado no GitHub da Microsoft e não precisou de instalação alguma. O Miasma plantou configuração com escopo de projeto em 73 repositórios da Microsoft — Azure/functions-action, a Action oficial de deploy, entre eles — e o payload executou no instante em que um desenvolvedor abriu o repositório no Claude Code, Cursor, Gemini CLI ou VS Code. Clonar não foi o gatilho. Abrir no seu agente foi.

Um desenvolvedor que clona Azure/functions-action para depurar um deploy que falha não hesita, e nem o agente que ele aponta para o repo. É um repositório de primeira parte da própria organização Azure da Microsoft — a fonte canônica da GitHub Action que metade do ecossistema referencia como Azure/functions-action@v1. Não há mantenedor a verificar, porque o mantenedor é a Microsoft. Não há nome a apertar os olhos, porque o nome está soletrado exatamente da forma que deveria estar. Então o repositório é aberto, e o agente lê a configuração do projeto da forma que toda ferramenta de codificação moderna faz ao abrir a pasta — e um desses arquivos de configuração aponta para um comando, e o comando é um blob de aproximadamente 4,3 megabytes que começa a ler o sistema de arquivos em busca de chaves.

Nós documentamos a metade npm desta mesma campanha há sete dias. O Miasma é o mesmo worm — uma variante do código «Mini Shai-Hulud» que o TeamPCP liberou publicamente em meados de maio — e o fato incômodo que ele demonstra é o mesmo, girado mais uma volta: fonte reputada não é um controle de segurança. Parece um. A maior parte dos conselhos de cadeia de suprimentos se apoia nele. E em 5 de junho ele não comprou absolutamente nada, em dobro: o namespace era da Microsoft, e a execução não veio de um pacote que você escolheu instalar. Ela veio de abrir uma pasta.

O que o Miasma fez com os repositórios da Microsoft.

Entre 5 e 6 de junho de 2026, o GitHub desativou 73 repositórios em quatro organizações GitHub da Microsoft após commits maliciosos serem empurrados para eles, segundo a The Hacker News e uma análise detalhada da StepSecurity. A Redmond Magazine cobriu o caso em 8 de junho. A distribuição:

  • Azure — 49 repositórios, incluindo Azure/functions-action (a Action oficial de deploy do Functions) e os language workers para .NET, Python, Java, Go e PowerShell.
  • microsoft — 10 repositórios.
  • Azure-Samples — 13 repositórios.
  • MicrosoftDocs — 1 repositório.

A forma, reduzida à sua mecânica:

  • O ponto de entrada foi uma conta de contribuidor previamente comprometida com acesso de commit, usada para empurrar commits maliciosos diretamente para os repositórios — marcados com [skip ci] para que as mudanças escorregassem por baixo das verificações de CI/CD que de outro modo teriam rodado.
  • O commit plantou configuração com escopo de projeto — o tipo de arquivo que um agente de codificação ou IDE lê e sobre o qual age automaticamente quando você abre a pasta: uma task de editor, um hook de agente, um servidor MCP definido pelo projeto. Esta é a mesma classe de fronteira de confiança que o TrustFall da Adversa AI demonstrou no Claude Code, Cursor CLI, Gemini CLI e Copilot CLI — todos os quatro executam configuração definida pelo projeto logo após o prompt de confiança da pasta.
  • O payload — aproximadamente 4,3 MB de código ofuscado — executou quando o repositório era aberto no Claude Code, Gemini CLI, Cursor ou VS Code, ou rodado através de um script npm test. Não apenas ao clonar. O ato de apontar seu agente para a árvore clonada é o que o rodou.
  • Na execução, ele varria o host em busca de tokens do GitHub, chaves AWS, service principals do Azure, credenciais GCP, tokens de publicação npm e PyPI, chaves SSH e arquivos .env, depois usava o acesso roubado para fazer commit de si mesmo adiante — que é o que o torna um worm em vez de um tiro único.

Um detalhe vale a pena demorar: Azure/durabletask estava entre os repositórios atingidos — e ele já havia sido comprometido em maio na campanha TeamPCP e limpo. Um repositório que foi remediado uma vez foi reenvenenado cinco semanas depois. Limpeza não é um estado que você alcança e mantém; é um estado do qual você recai no instante em que outra credencial na cadeia é tomada.

Vale ser igualmente preciso sobre o que não aconteceu. A rede corporativa da Microsoft não foi violada. O Azure, o serviço de nuvem, não foi violado. Nenhum dado de cliente e nenhum sistema de produção foi tocado. Isto foi um ataque a repositórios de código-fonte — e sua consequência mais sentida não teve nada a ver com o malware: no instante em que o GitHub desativou Azure/functions-action, todo pipeline da Terra que referenciava Azure/functions-action@v1 parou de resolver. A Microsoft foi o portador de maior visibilidade. As pessoas de fato comprometidas foram os desenvolvedores que abriram os repositórios envenenados em um agente entre 3 e 5 de junho, e tiveram suas credenciais varridas das próprias máquinas.

LAPTOP DESENVOLVEDOR — segredos do host visíveis ao que o agente rodar ao abrir a pastaGITHUB DA PRÓPRIA MICROSOFTconta de contribuidorcomprometida → push [skip ci]Azure/functions-actionAzure/durabletask (de novo)+ config de projeto plantadaDESENVOLVEDOR CLONAgit clone …/functions-actionnada roda aindarepo de primeira parte ⇒sem razão para hesitarABERTO NO AGENTE — o payload disparaClaude Code · Cursor · Gemini CLI · VS Codeabrir pasta → lê config do projetohook de agente / task / servidor MCP↳ executa payload de 4,3 MB(só clonar NÃO o rodou)FILESYSTEM & ENV DO HOST — tudo real, tudo legível pelo payload$GITHUB_TOKEN, gh hosts.ymlacesso de push (real)~/.aws, service principals Azurechaves de nuvem (real)~/.npmrc, token PyPIpublica aqui (real)~/.ssh/id_ed25519, .envchaves + segredos CI (real)tar | encrypt | exfiltrate→ colhidos da sua máquinaAUTOPROPAGAÇÃOacesso GitHub roubadocommit no próximo repoplanta a mesma config73 repos, 4 orgsdurabletask: atingido 2x
O Miasma em uma máquina de desenvolvedor que abre um repositório afetado da Microsoft em um agente de codificação. Uma conta de contribuidor previamente comprometida empurra um commit [skip ci] que planta configuração com escopo de projeto — um hook de agente, uma task de editor, uma definição de servidor MCP. O desenvolvedor clona o repositório (nada roda) e o abre no Claude Code, Cursor, Gemini CLI ou VS Code. Ao abrir a pasta, o agente lê e age sobre essa configuração, executando um payload de 4,3 MB que varre o host em busca de tokens do GitHub, chaves AWS, service principals do Azure, tokens npm/PyPI, chaves SSH e arquivos .env, criptografa-os, envia-os para fora, e usa o acesso GitHub roubado para fazer commit de si mesmo no próximo repositório. Sem typosquat, sem mantenedor falso, sem npm install. O sinal de confiança é o nome da org Microsoft, e a execução vem de abrir uma pasta.

O mesmo repositório, aberto dentro do Bromure Agentic Coding.

O Bromure Agentic Coding roda seu agente de codificação dentro de uma VM Linux por perfil — seu próprio kernel, seu próprio sistema de arquivos, sua própria pilha de rede, no framework de Virtualização da Apple. Um perfil é um escopo coerente de trabalho: este cliente, este serviço interno, este repositório open-source que você clonou para depurar um deploy. Você clona Azure/functions-action dentro desse perfil e o abre com seu agente ali dentro. O gatilho de abrir a pasta dispara exatamente como projetado. O payload roda. E então ele vai procurar chaves em um host que não consegue alcançar.

Porque as credenciais não estão no perfil. A VM vem com stubs — tokens falsos que parecem reais para git, gh, aws, kubectl, npm e qualquer outra coisa que espere um cabeçalho Authorization. Um proxy no seu Mac fica à frente de cada conexão que deixa o sandbox, reconhece o stub e o troca pelo segredo real no fio à medida que a requisição sai (o sandbox que segurava a chave percorre o mecanismo). O PAT real do GitHub, a chave AWS real, o principal Azure real — nenhum deles toca um arquivo, uma variável de ambiente ou uma página de memória que a VM possa ler. As chaves SSH nunca deixam o Keychain do macOS; apenas o socket do ssh-agent é encaminhado para dentro, do jeito que o OpenSSH sempre pretendeu.

Então percorra a varredura do Miasma através dessa fronteira. O payload lê o ambiente em busca de $GITHUB_TOKEN e encontra um stub. Ele lê ~/.aws e não encontra nada. Ele lê ~/.npmrc e não encontra token de publicação. Ele lê ~/.ssh e não encontra arquivo de chave — há um socket encaminhado, não uma chave privada no disco. O blob de 4,3 MB roda até a conclusão exatamente como escrito. Ele apenas exfiltra uma caixa que nunca esteve segurando suas chaves, do lado errado de uma fronteira imposta por hardware em relação a tudo que importa.

VM BROMURE POR PERFILagente abre functions-actionconfig do projeto → payload dispararoda, mas só dentro do convidadoO QUE O PAYLOAD VÊ$GITHUB_TOKENstub_7f3a…~/.aws, ~/.npmrcstub / ausentearquivo de chave ~/.sshsó socketpropaga: git push (de si)precisa escrever → pede ao proxyexfil chaves host: nada a levarprecisa de uma dependência?npm install → busca deixa a VMatravés do proxy do BromurePROXY · SEU MACINTERMEDIÁRIO DE CRED.PAT / chaves reais aquistub → real, no fiovalor nunca entra na VMGUARDRAIL: LEITURA/ESCRITApush = mutar → PROMPTnomeia verbo + alvoCADEIA DE SUPRIMENTOSscan OSV + socket.devcooldown: < 2 dias retidoscripts de install removidosRESULTADOchaves do host:stubs colhidos,reais intocadaspush p/ propagar:pausado, você diz nãopacote ruim recente:nunca chega à VMraio de explosão = 1 perfil
Três camadas, na ordem em que o Miasma as atinge. (1) Cadeia de suprimentos: o proxy escaneia cada busca de pacote contra o OSV e o socket.dev e coloca em quarentena releases mais jovens que o cooldown — de modo que o agente nem consegue puxar uma dependência maliciosa recente enquanto o ecossistema ainda está se atualizando. (2) Intermediação de credenciais: a VM guarda apenas stubs; os segredos reais ficam no host atrás do proxy, trocados no fio, então a varredura do payload no host encontra placeholders. (3) Guardrails: o passo de propagação do worm — um git push para fazer commit de si mesmo adiante — é uma chamada que altera estado, e um guardrail de leitura/escrita o detém no fio e pergunta, nomeando o verbo e o alvo. Cada camada é imposta abaixo do agente, na fronteira da VM que o agente não consegue contornar.

O passo de propagação é uma escrita, e escritas recebem um prompt.

Roubar chaves é apenas metade do Miasma. A outra metade é propagação: ele usou o acesso GitHub roubado para fazer commit de si mesmo no próximo repositório, e foi isso que transformou um punhado de repositórios envenenados em 73. Mesmo em um perfil que legitimamente tem acesso de push — digamos que você clonou functions-action justamente porque pretende abrir um PR contra ele — o passo de propagação do worm ainda tem que sair através do proxy, e é aí que os Guardrails o encontram.

Os Guardrails leem a operação, não apenas a conexão — eles distinguem uma leitura de uma escrita. Um git fetch é uma leitura; um git push é uma escrita. Defina a credencial GitHub de um perfil como perguntar ao escrever, e no instante em que o agente busca uma chamada que altera estado — o git-receive-pack de que o worm precisa para fazer commit de sua config de volta, um DELETE contra uma API, um Terminate* no EC2 — o Bromure o detém no fio e exibe um prompt no seu Mac que nomeia o verbo, o alvo e o perfil. A concessão que você dá é limitada no tempo: quinze minutos para um release, uso único para as assustadoras, nunca se o pedido não faz sentido. Leituras nunca interrompem você; o agente busca, faz grep e lê o dia todo. É a mutação que pausa.

Esta é a diferença entre «o agente tem um token» e «o agente pode fazer o que quiser com o token». O mecanismo inteiro de propagação do Miasma é uma escrita que o agente nunca te disse que estava fazendo — e uma escrita que o agente nunca te disse que estava fazendo é exatamente o que o prompt de leitura/escrita foi construído para pegar. O push que propaga o worm vira uma caixa de diálogo na qual você clica Não permitir, do mesmo jeito que «o agente apagou o banco de dados de produção» deixa de ser um postmortem e vira um prompt que você recusou.

A versão tinha horas de idade, e o Bromure faz os pacotes envelhecerem.

Há um segundo modo pelo qual o Miasma — e a linhagem mais ampla Mini Shai-Hulud — alcança um desenvolvedor: não através de um repositório que você abre, mas através de um pacote recém-envenenado que o agente instala enquanto faz seu trabalho. A metade Red Hat desta campanha foi precisamente isso, um hook preinstall em 32 pacotes em um escopo confiável. E o detalhe brutal daqueles incidentes é o tempo: uma versão comprometida tipicamente é pega e removida em questão de horas — mas essas são exatamente as horas durante as quais um agente autônomo, rodando sem supervisão, pode puxá-la.

A camada de Cadeia de Suprimentos do Bromure transforma o mesmo proxy de fronteira em um ponto de checagem de varredura, e faz as duas coisas que de fato importam contra um comprometimento do mesmo dia:

  • Ela força a varredura de cada busca contra o socket.dev além do OSV. O OSV pega CVEs conhecidos acima do limiar de severidade que você define. O socket.dev pega o que os bancos de dados de vulnerabilidade ainda não alcançaram — scripts de install maliciosos, malware comportamental, typosquats, o comprometimento recém-publicado. Um release sinalizado é bloqueado antes de o tarball sequer aterrissar na VM. Crucialmente, a varredura roda abaixo do agente, no proxy: por mais que o agente reescreva sua própria config para te contornar, a busca ainda sai pela fronteira que ele não consegue cruzar.
  • Ela impõe um cooldown. O Bromure coloca em quarentena qualquer release publicado nos últimos dois dias — ajustável — de modo que uma versão enviada há uma hora simplesmente não é instalável nesse perfil enquanto o ecossistema se atualiza. Contra um worm cuja janela inteira de oportunidade é o intervalo entre publicar e remover, um cooldown não é uma heurística sobre se um pacote parece ruim. É uma recusa a ser o primeiro a descobrir. Combine-o com a remoção de scripts de install que o Bromure faz na hora — tirando hooks postinstall do tarball e corrigindo o hash de metadados para que a instalação ainda verifique — e o pacote que de fato aterrissa aterrissa inerte.

Para o Miasma especificamente, o vetor de abrir-repositório é a manchete. Mas a mesma campanha se espalha também por pacotes, e o cooldown é o controle que teria matado de fome o lado npm dela: um release recente de @redhat-cloud-services, ou uma dependência transitiva recém-envenenada puxada enquanto se depurava aquele repositório da Microsoft, fica em quarentena justamente pelas horas em que é perigoso.

Onde isto não te salva.

Um push que você aprova é um push que acontece.

O guardrail de leitura/escrita pega a escrita que o agente não te contou. Ele não lê o diff. Se você está legitimamente empurrando para functions-action e aprova o prompt, o Bromure encaminha o push — incluindo, em princípio, um workflow envenenado que você não notou no diff. Leia o que você aprova. O trace de sessão te diz quais diffs ler.

O cooldown é uma janela, não uma parede.

Dois dias está ajustado ao intervalo observado entre publicar e remover, mas um atacante paciente pode ficar sentado em uma versão comprometida por mais tempo que o cooldown e ainda ser instalável no terceiro dia. O cooldown mata de fome os worms do mesmo dia; ele não atesta um pacote que apenas ficou velho. O socket.dev e o OSV ainda têm que fazer a parte deles.

O perfil é de longa duração, então a persistência persiste.

Um perfil Bromure não é um disco descartável. Um payload que se escreve em um caminho de inicialização dentro do perfil pode sobreviver até a próxima sessão nesse perfil. O que ele acorda encontrando é um convidado sem chaves do host e um intermediário que só fala em tokens de curta duração, com prompt e escopo limitado — presença em uma caixa sem nada dentro — mas presença ainda assim.

Defina o escopo do intermediário de propósito.

Se um perfil está provisionado para empurrar para um repositório hoje e esse perfil roda o Miasma hoje, uma escrita aprovada passa. As concessões do intermediário funcionam porque são estreitas. Um perfil que só precisa ler um repositório não deveria poder escrevê-lo; um perfil que nunca publica não deveria guardar token de publicação. O isolamento contém a explosão; o escopo decide quão grande ela poderia ter sido.

O próximo repositório confiável já está clonado em algum lugar.

A lição do worm TanStack foi que o lockfile e a assinatura não são defesas. A lição do escopo da Red Hat foi que nem o publicador é. A Microsoft adiciona o próximo corolário: nem o repositório é, e o gatilho nem precisa ser uma instalação que você escolheu — pode ser uma pasta que seu agente abriu. O repositório Azure/functions-action não fez nada de errado ao ser confiável. Ser confiável é o propósito inteiro de uma Action canônica de primeira parte, e é exatamente o que o tornou digno de envenenar — duas vezes, no caso de durabletask.

Você não pode consertar isso confiando com mais cuidado, porque a confiança nunca esteve mal colocada. Você conserta arranjando as coisas de modo que «qual repositório é este» e «qual escopo publicou isto» deixem de ser as perguntas das quais seu keychain depende. O Bromure Agentic Coding é a configuração onde o agente abre o repositório dentro de uma VM por perfil, as credenciais reais ficam no host atrás de um intermediário, cada escrita que o agente faz tem que passar por um prompt, e um pacote não pode ser instalado até sobreviver a um cooldown. O pior que uma abertura de pasta envenenada pode fazer é exfiltrar uma caixa que nunca esteve segurando suas chaves. É gratuito, open-source, e disponibilizado hoje.