Ele pediu para ser executado
A Sonatype está rastreando 846 pacotes npm maliciosos publicados a partir de centenas de contas descartáveis, com nomes gerados por IA. Nenhum deles traz script de instalação. O README manda você carregar o pacote com require(), e o import executa o loader. As defesas que reconhecem um pacote ruim pelo nome, pelo autor ou pelo hook declarado agora estão correndo atrás de um gerador. O Bromure Agentic Coding bloqueia pela única propriedade que um gerador não consegue forjar: a idade do pacote.
Os pacotes não trazem nenhum hook de instalação, então o npm não tem o que
interceptar. O README manda carregá-los com require(), e o import executa
o loader. Cada vez mais, o leitor que segue essa instrução é um agente de
código.
A Sonatype Research Labs divulgou em 5 de agosto uma campanha que chama de Flooding Dropper, contando 846 componentes npm maliciosos e a conta ainda subindo. Paul McCarty, do OpenSourceMalware, rastreia o payload sob o nome WEL1DROPPER. A cobertura mais ampla saiu em 7 de agosto.
Dois detalhes a separam das histórias de supply chain do npm do último mês.
O primeiro é como os pacotes ganham seus nomes. O atacante os gera, rodando
slopsquatting como uma linha de produção. Radicais recorrentes como bigops e
bnpl se recombinam com outras palavras em nomes como bigops-api e
dolyame-boxy-desktop-bnpl-card-gallery, muitos deles compartilhando um
padrão de versão 35.x.y. A publicação se espalha por centenas de contas
descartáveis com um punhado de pacotes cada, de modo que não há um autor
prolífico para banir.
Sem script de instalação
Os pacotes não declaram nenhum hook de ciclo de vida, então o npm install
não executa nada. O texto de McCarty diz isso sem rodeios: “Os pacotes NPM
não usam script preinstall nem postinstall. Não é preciso.”
Eles trazem um _helpers.js empacotado que roda quando o módulo é importado,
junto com um README que manda o leitor importá-lo. Carregue o pacote com
require(), diz a documentação, e a documentação está certa. O primeiro
estágio faz o fingerprint do sistema operacional e da arquitetura do
processador, puxa um segundo estágio nativo por HTTPS de um entre três hosts
Cloudflare Workers, e recorre a remontar o binário a partir de pedaços base64
guardados em registros DNS TXT sob wel1.ru quando a busca direta falha.
O segundo estágio varia por plataforma. O Linux recebe um ELF empacotado com UPX que instala um implante C2 Sliver. O macOS recebe verificações de antidepuração, uma sondagem por VMware, um binário de beacon e persistência via LaunchAgent. O Windows recebe patching de ETW e AMSI, detecção de sandbox, execução reflexiva em memória e persistência por uma chave Run do Registro e uma tarefa agendada. McCarty e Sonatype leem a campanha como uma evolução da “Moika”, a onda de confusão de dependências de abril que empurrou uns 250 pacotes construídos em torno do roubo de ambiente.
O atacante tirou o gatilho do gerenciador de pacotes e o colocou no leitor.
A automação vence a deny list
O próprio resumo da Sonatype é a frase mais afiada de toda a cobertura:
Leia isso como uma afirmação sobre defesas. Toda camada que protege você reconhecendo o pacote ruim, seja pelo nome, pela conta de publicação ou pelo hook declarado, agora compete com um gerador. Essas camadas continuam pegando o que já viram. Elas são avaliadas por reconhecer uma string específica, e o fornecimento de strings específicas custa uma execução de script.
O ângulo do agente afia tudo isso. O slopsquatting compensa em primeiro lugar porque modelos de linguagem inventam nomes de pacote que soam certos e não existem, e um atacante que registra esses nomes recolhe tráfego de instalação de graça. Some o hook de instalação ausente e você tem um pacote cujo gatilho é uma instrução escrita num README. Um agente de código é o leitor que vai alucinar o nome, achar um pacote debaixo dele, ler a documentação e fazer o que a documentação manda. A campanha é moldada para o agente nas duas pontas.
Escolha uma propriedade que o gerador não consiga forjar.
Idade não é um nome
Todo pacote desta campanha compartilha uma propriedade que nenhuma automação muda: ele é novo. Um nome que um modelo inventou na terça passada não pode ter sob ele um pacote de 2019. Centenas de contas descartáveis publicam centenas de versões recém-saídas, porque uma conta descartável não tem histórico de onde publicar. O modelo depende de o pacote estar lá quando alguém for buscá-lo, então o atacante o cria poucos dias antes, no máximo.
O Bromure Agentic Coding roda o agente dentro de uma VM Linux descartável em Apple Silicon e roteia cada byte do seu tráfego de rede por um proxy no host, fora da caixa em que o agente roda. As buscas de pacote de npm, PyPI, Cargo, RubyGems, Maven, NuGet, módulos Go e Packagist atravessam todas esse proxy, e a primeira política que encontram é um relógio.
A trava de idade, ligada por padrão
O Bromure recusa pacotes mais novos que uma idade mínima, definida em dois
dias de fábrica. Referências flutuantes como latest ou um intervalo
semver resolvem para a versão mais recente anterior ao corte, de modo que
instalações comuns continuam funcionando e caem no lançamento de ontem.
Uma referência fixada a uma versão fresca demais recebe um 451 com um erro
claro do Bromure nomeando o que aconteceu. Oitocentos pacotes custam tanto
para parar quanto oito, porque o Bromure não reconhece nenhum deles.
Ela roda antes de a discussão começar
A parte esperta desta campanha, largar o hook de ciclo de vida e mover a
execução para o require(), descreve o que acontece depois que o tarball
chega ao disco. A trava de idade fica na busca, no proxy do host, acima de
tudo isso. Se o payload teria rodado na instalação ou na importação é uma
pergunta sobre um pacote que nunca chegou. O .npmrc e o pip.conf
dentro da VM podem apertar essa política e não podem afrouxá-la.
Reputação, atrás do relógio
Com uma chave do socket.dev, o Bromure confere cada pacote contra a base do socket.dev antes de liberar a busca e bloqueia os marcados como comprometidos, malware ou typosquats. Consultas OSV bloqueiam pacotes que carregam vulnerabilidades conhecidas a partir de uma severidade que você escolhe. O Delpi redireciona toda requisição npm para um registry de filtragem. Essas camadas reconhecem coisas, o que as torna úteis e as coloca em segundo lugar.
O README é pontuado pelo que ele é
“Carregue isto com require()” é uma instrução escrita por um estranho,
chegando em texto que o agente lê. O Bromure pontua a saída de ferramentas
que o agente consome, incluindo conteúdo de arquivos, páginas web e
documentação buscada, com um modelo local de injeção de prompt. Ele pontua
os arquivos de instrução que o agente trata como autoridade, CLAUDE.md e
AGENTS.md e suas variantes, com um classificador dedicado mais um scanner
de payloads em Unicode invisível. Nada sai do Mac para isso.
O payload traça o perfil da máquina errada
Suponha que a busca passe mesmo assim, porque o pacote envelheceu além do corte ou uma entrada de allowlist o dispensa. Siga o WEL1DROPPER por um perfil Bromure e veja-o fazer seu trabalho contra uma máquina que você ia jogar fora.
O primeiro movimento do loader é fazer o fingerprint do host. Num perfil
Bromure, o host que ele mede é uma VM Ubuntu em NAT, a um hipervisor de
distância do macOS, então ele toma o ramo Linux e planta ali um implante
Sliver. Ele nunca pede o ramo macOS: a antidepuração, a sondagem por VMware, o
download do beacon_mac.bin, o LaunchAgent que sobreviveria a um reboot, o
interesse por domínios russos de pagamento. Tudo isso é código para uma
máquina que o dropper nunca viu. O Erase home descarta a camada em que ele
pousou e devolve o diretório home ao estado logo após o clone.
Então o estágio infostealer vai atrás do que seu ancestral Moika foi atrás: o
ambiente. Num perfil Bromure, o ambiente guarda placeholders brm_…. Nenhuma
das chaves reais de Anthropic, OpenAI e xAI, nenhum dos tokens do GitHub e do
GitLab, nenhum material da AWS nem credencial de registry fica na memória da
VM ou nos seus dotfiles. O Bromure os mantém no host e os substitui na
requisição no proxy, no fio, para o único host de destino a que cada um
pertence. O roubo roda até o fim e despacha strings que só resolvem em um Mac,
que não é a máquina que executou o código.
A caçada que eles recomendam, já rodando
McCarty fecha o texto sobre o WEL1DROPPER com conselhos defensivos: caçar
consultas DNS TXT para wel1.ru, revisar logs de proxy em busca de tráfego
para os hosts Cloudflare Workers, e procurar arquivos temporários e
LaunchAgents perdidos.
Leia o segundo item de novo. Seu laptop não mantém log de proxy. Um perfil
Bromure mantém um por construção: toda requisição que a VM faz atravessa o
proxy do host, e o Security Log e o Trace Inspector registram host, status,
latência e relatório de substituição de cada uma. Três destinos
*.workers.dev desconhecidos aparecendo durante um build são três linhas numa
lista que você pode ler, na máquina em que o build rodou, sem implantar nada
para obtê-las.
Os registries vão continuar derrubando esses pacotes, e as derrubadas funcionam: 846 deles sumiram ou estão sumindo. Mas a campanha já passou de mil, publicada a partir de contas que não custam nada para criar sob nomes que não custam nada para inventar, e a próxima onda está a uma execução de script de distância. Reconhecimento é a competição errada para entrar.
Um pacote de dois dias não merece uma confiança profunda que falte a um de duas horas. Ele fica fora da janela em que essa economia opera, e você pode mover seu grafo de dependências para trás dessa janela com um ajuste que já está ligado.
Instale o Bromure Agentic Coding, e deixe a busca encontrar um relógio antes de encontrar sua máquina.