5 pontos por GN⁺ 2025-09-17 | 1 comentários | Compartilhar no WhatsApp
  • Um ataque à cadeia de suprimentos no ecossistema NPM injetou malware autorreplicante em mais de 40 pacotes, incluindo o popular @ctrl/tinycolor, o que pode causar infecção em cascata de segredos do ambiente de desenvolvimento e até de credenciais de CI/CD. As versões infectadas já foram removidas do npm
  • O payload do ataque executa de forma assíncrona um bundle Webpack (bundle.js, ~3.6MB) durante o processo de instalação via npm e realiza coleta abrangente de credenciais por meio de variáveis de ambiente, sistema de arquivos e SDKs de nuvem
  • A lógica maliciosa usa NpmModule.updatePackage para forçar patch e publicação de outros pacotes, promovendo propagação em cascata, e injeta o workflow shai-hulud no GitHub Actions para roubar segredos da organização com toJSON(secrets)
  • Os dados coletados são exfiltrados por meio da criação do repositório público no GitHub 'Shai-Hulud', disfarçando-se como atividade normal de desenvolvimento e com alta capacidade de evasão de detecção
  • A ação é executada de forma furtiva com acesso a endpoints de metadados e tokens de AWS/GCP/Azure/NPM/GitHub, além de busca de segredos baseada em TruffleHog
  • É exigida a remoção imediata dos pacotes, limpeza dos repositórios e substituição de todas as credenciais, além de verificação de logs do CloudTrail/GCP Audit, bloqueio de webhooks e adoção de proteção de branch/Secret Scanning/políticas de cooldown

Affected Packages

  • Foi reportado um total de 195 pacotes/versões, incluindo principalmente @ctrl/tinycolor(4.1.1, 4.1.2), vários pacotes no namespace @ctrl/, módulos da família @crowdstrike/, além de ngx-bootstrap/ngx-toastr/ng2-file-upload/ngx-color em todo o ecossistema de Angular/UI web, a stack mobile @nativescript-community/ e @nstudio/, a toolchain de ciências da vida teselagen/, além de ember-*, koa2-swagger-ui, pm2-gelf-json e wdio-web-reporter
  • Para cada pacote, é necessário consultar a tabela do texto original para verificar com precisão se as versões exatas estão em uso
    • Exemplo: @ctrl/ngx-emoji-mart 9.2.1, 9.2.2, @ctrl/qbittorrent 9.7.1, 9.7.2, ngx-bootstrap 18.1.4, 19.0.3–20.0.5, ng2-file-upload 7.0.2–9.0.1 etc., em ampla escala

Immediate Actions Required

Identify and Remove Compromised Packages

  • Verifique se há pacotes infectados no projeto: use npm ls @ctrl/tinycolor e comandos semelhantes
  • Remova imediatamente os pacotes infectados: execute npm uninstall @ctrl/tinycolor e equivalentes
  • Verifique vestígios locais buscando o hash conhecido do bundle.js: use sha256sum | grep 46faab8a...

Clean Infected Repositories

  • Remova o workflow malicioso do GitHub Actions: exclua .github/workflows/shai-hulud-workflow.yml
  • Detecte e remova o branch remoto shai-hulud criado: execute git ls-remote ... | grep shai-hulud e depois git push origin --delete shai-hulud

Rotate All Credentials Immediately

  • É necessária a substituição completa de tokens do NPM, segredos do GitHub PAT/Actions, chaves SSH, credenciais de AWS/GCP/Azure, strings de conexão de banco de dados, tokens de terceiros e segredos de CI/CD
  • Também é necessária a rotação completa dos itens armazenados em AWS Secrets Manager/GCP Secret Manager

Audit Cloud Infrastructure for Compromise

  • AWS: no CloudTrail, verifique horários e padrões de chamadas BatchGetSecretValue, ListSecrets e GetSecretValue, e confirme criação/uso anormal de chaves com o IAM Credential Report
  • GCP: use os Audit Logs para revisar acessos ao Secret Manager e verificar a existência de eventos CreateServiceAccountKey

1 comentários

 
GN⁺ 2025-09-17
Comentários do Hacker News
  • Do ponto de vista de quem usa pacotes hospedados no npm, não parece realista monitorar manualmente todas as dependências e as dependências das dependências; além disso, como não sou especialista em TypeScript/JavaScript, acho que não conseguiria identificar facilmente um malware escondido por um atacante. Ultimamente venho pensando em atualizar em um "modo de atraso", ou seja, atualizar não para a versão mais recente, mas apenas para versões que já tenham passado de um certo período. A ideia é que, se um pacote ficou exposto por umas 6 semanas, há uma boa chance de que um malware já tenha sido descoberto. Não é um método perfeito, mas seria bom existir uma ferramenta que também permitisse, em caso de problema de segurança, aplicar imediatamente a atualização mais recente como exceção

    • Há um recurso citado diretamente no artigo: NPM Package Cooldown Check. Se uma versão de pacote lançada dentro do período definido pela organização (2 dias por padrão) for adicionada em um pull request, o build falha automaticamente. Como a maioria dos ataques à cadeia de suprimentos é detectada em até 24 horas, até uma espera bem curta já pode reduzir a exposição de segurança

    • Como é difícil inspecionar todas as dependências, eu defenderia reduzir ao máximo o número delas e usar apenas pacotes bem conhecidos e confiáveis. Na verdade, a menos que você esteja em um ambiente controlado o suficiente para confiar em todos os autores, manter um certo nível de "not-invented-here" é uma escolha sensata

    • Tenho o hábito de fixar explicitamente as versões no package.json e usar npm ci para instalar apenas as versões especificadas no package-lock.json. Também executo npm audit no CI para receber alertas caso apareça alguma vulnerabilidade em um pacote. Assim, os pacotes ficam praticamente em estado de "congelamento", e a idade do pacote por si só já reduz a chance de infecção

    • No meu caso, vou além disso e só atualizo dependências quando um bug realmente afeta meu ambiente de uso. Mesmo que haja uma vulnerabilidade de segurança, se ela não me afeta, eu simplesmente deixo passar. A maioria dos desenvolvedores atualiza dependências com frequência desnecessária, mas acho que o certo é fazer isso apenas quando realmente for preciso. Se um pacote exige atualizações frequentes ou é complicado de manter, eu prefiro nem usá-lo ou então "congelá-lo" segundo meus critérios

    • Dá para limitar atualizações de forma parecida usando o uv do Python. Por exemplo, com um comando como uv lock --exclude-newer $(date --iso -d "2 days ago"), é possível excluir versões lançadas nos últimos 2 dias

  • Esse problema acontece porque pacotes ou versões novas não são monitorados. O ideal seria operar separando, como no Debian, uma distribuição estável que recebe apenas patches de segurança e correções de bugs, e distribuições testing/unstable monitoradas pelos mantenedores dos pacotes. Todo mundo que trabalha com repositórios abertos e centralizados de pacotes (NPM, Python, Rust etc.) sofre com o mesmo problema

    • Há um problema na cultura de desenvolvimento. Ter centenas de dependências (inclusive transitivas) e atualizá-las automaticamente sem pensar é o problema em si. Escolher expor o ambiente de build/execução a tanto código de terceiros traz responsabilidade

    • As distribuições também sentem cada vez mais o peso da quantidade de pacotes que precisam manter. Na verdade, ecossistemas por linguagem (como CPAN, Maven, RubyGems etc.) cresceram justamente por isso. Só as distribuições Linux não conseguiam oferecer os apps que os usuários queriam, então surgiram vários caminhos como freshmeat, linuxbrew, flatpak e PPA. Não acho que toda comunidade tenha capacidade de monitorar e dar suporte a vários branches de uma enorme variedade de bibliotecas

    • Como desenvolvedor Debian, está ficando cada vez mais difícil detectar mudanças reais antes de incorporar código upstream, por causa de tanto "ruído" crescente — principalmente mudanças puramente de estilo e atualizações de tooling. Gostaria que esse tipo de mudança fosse evitado, a menos que seja uma refatoração que realmente precise de revisão humana, uma correção de bug, uma adição de funcionalidade ou o resultado de ferramentas usadas para encontrar código problemático

    • No Rust existe um sistema chamado cargo vet. Empresas como Google e Mozilla participam dele, compartilhando e verificando pacotes de forma automatizada

    • Acho que há formas de manter a descentralização e ainda assim colocar algumas proteções. Por exemplo, exigir aprovação de duas contas com 2FA para pacotes acima de certo porte, ou permitir que pacotes populares sejam enviados ao npm apenas por meio de sistemas de build reproduzíveis. Não seria abandonar totalmente a distribuição descentralizada, só exigir um pouco mais de esforço em projetos grandes

  • Por causa da sequência recente de ataques à cadeia de suprimentos, estou considerando com mais seriedade renderização no servidor, sem JavaScript. Graças ao HTMX, percebi que dá para ir muito longe sem JavaScript. E, fazendo isso, a aplicação talvez fique até mais rápida e mais estável

    • Quero enfatizar que o ambiente JS tradicional é, na prática, o sandbox mais seguro que existe. Há quase 30 anos, código JS não confiável roda em bilhões de dispositivos, mas os casos de ataques em larga escala bem-sucedidos contra engines de navegador são pouquíssimos. Já o ambiente de NodeJS e npm precisa de uma reformulação completa em termos de segurança. Casos como o do leftpad vêm de uma cultura em que até snippets minúsculos de código são publicados no npm

    • Acho estranho como esses ataques tendem a ser automaticamente reduzidos a um problema de um ambiente específico, o JavaScript. Na verdade, um problema maior é que nem mesmo as medidas de endurecimento de segurança que já existem no npm foram aplicadas em outros ecossistemas, como PyPI e Crates

    • Fazer vendoring pode reduzir a exposição, mas não resolve o problema na raiz. Se o NPM levasse segurança a sério, exigiria 2FA para publicar, varredura prévia dos pacotes e até assinatura com chave de hardware. Semver ou CRC não bastam. Tudo isso deveria vir embutido por padrão no sistema de gerenciamento de pacotes

    • Na verdade, esse tipo de ataque não é um problema exclusivo do JavaScript; ele vem do fato de que os desenvolvedores não monitoram o suficiente quando adicionam uma nova dependência. Isso pode se aplicar igualmente a outros ecossistemas, como Rust e Go

    • Toda linguagem que depende fortemente de gerenciador de pacotes e tem uma biblioteca padrão fraca é vulnerável da mesma forma. No longo prazo, acho que deveríamos voltar mais ao JavaScript vanilla. Rust também depende muito de pacotes. Na verdade, Go é um caso exemplar nesse aspecto

  • Acho que precisamos de um sistema para rastrear código leve que assine commits/releases com chaves confiáveis, além de instalar e verificar isso. Já existe a proveniência do npm usando sigstore, mas ainda não parece ser amplamente usada e parece limitada mais à verificação do publicador

  • Essa vulnerabilidade já havia sido reportada ao NPM em 2016 (aviso do CERT), mas a resposta do NPM foi WAI (working as intended)

    • Para quem não sabe o que WAI significa: em geral é a sigla de “working as intended”

    • Mesmo que não exista nenhum script de postinstall, parece que o malware acabaria sendo executado do mesmo jeito quando o módulo fosse importado no processo de build, na inicialização do servidor, nos testes etc. Afinal, depois de npm install, inevitavelmente chega um momento em que algo de fato é executado...

  • Lembrei de um comentário que vi aqui na época do caso left-pad: falava de um mantenedor famoso do npm que tinha 600 pacotes npm e 1.200 linhas de código JavaScript. Um caso que eu gostaria de citar como exemplo é o esbuild, que quase não tem dependências externas e usa apenas a biblioteca padrão do Go.
    Outros projetos ditos de "próxima geração" também têm cadeias de dependência relativamente pequenas, como biomejs e swc. Mas, olhando o código-fonte original em Rust, biomejs e swc acabam tendo muitas dependências também. Se projetos assim se espalharem, imagino que o ecossistema cargo vá seguir o mesmo caminho. Se alguém conhecer algum projeto grande escrito com uma disciplina rígida como a do esbuild, eu adoraria recomendações

    • Um dos motivos de eu ter migrado para Go foi a tendência de bibliotecas no estilo purego. Em geral, dependem só da biblioteca padrão e de golang.org/x, e podem ser compiladas sem CGO, o que dá uma ótima portabilidade. go mod vendor ajuda no gerenciamento de risco no curto prazo, mas não é uma solução fundamental. O Go também não oferece verificação de pacotes de ponta a ponta (assinaturas/checagem de chaves etc.), então a vulnerabilidade continua existindo. Isso parece especialmente concentrado em infraestrutura de CI/CD, mas se fosse possível buildar e implantar sem distribuir a chave de assinatura, a segurança também poderia melhorar. Acho que gerenciadores de pacotes deveriam incentivar assinaturas GPG, e que commits de git também deveriam ser assinados antes da distribuição

    • O caso do eslint é particularmente frustrante. Olhando o grafo de dependências, ele é enorme, e se os mantenedores não priorizarem reduzir dependências, no fim só resta migrar para outra solução, como o oxlint

    • A resposta é implementar por conta própria as funcionalidades simples e reduzir dependências externas. Só isso normalmente já corta uns 2/3 do total de dependências. Coisas simples como left-pad é melhor fazer você mesmo e manter sob seu controle, com unidades pequenas e testes; o custo de manutenção também não é tão grande assim. Dependências desnecessárias devem ser eliminadas sem medo

    • O que aparece no Cargo.toml raiz de um projeto Rust é para o workspace inteiro, e as dependências reais de cada crate individual costumam ser bem mais rasas. É preciso investigar mais a fundo para entender a estrutura real das dependências

    • O lado ruim é que agora, para inspecionar projetos JavaScript, também é preciso saber ler Golang. E ainda por cima um post-install executa de novo node install.js, então no fim não resta muita alternativa além de confiar completamente ou ler todo o código

  • Não consigo acreditar que o npm ainda execute por padrão scripts de postinstall de todas as dependências. Pnpm e Bun só executam quando estão em uma lista de permissões, e o Composer simplesmente não executa scripts de ciclo de vida para dependências. Acho essa abordagem mais segura, dado o risco que pacotes dependentes representam em ambientes de build ou desenvolvimento

    • Fico curioso sobre por que esse tipo de ataque em grande escala não aparece com tanta frequência em outros gerenciadores de pacotes, como Rust build.rs, Python ou Java. Não é só postinstall; em princípio, isso é possível em quase todo ecossistema, mas os incidentes parecem se concentrar no npm

    • Vi que o padrão do Pnpm mudou para bloquear scripts. Tenho curiosidade sobre a reação da comunidade — questões de experiência de uso, abuso do comando allow etc. — e sei que discussões parecidas estão acontecendo também na comunidade de empacotamento Python em torno de wheel variants. Gostaria de conhecer a experiência de outros ecossistemas

  • Este ataque já se espalhou para mais de 180 pacotes; veja o blog da Aikido Security

  • Fico curioso sobre quem foi a primeira pessoa a descobrir esse ataque. É interessante como blogs diferentes atribuem o mérito de formas diferentes. Aikido diz “descobrimos um ataque em larga escala”, e Socket, Ox, Safety, Phoenix, Semgrep e outros também descrevem isso cada um à sua maneira

    • Eu sou Mackenzie, da Aikido. A primeira pessoa a reportar esse caso foi o desenvolvedor Daniel Pereira, que levou a informação à Socket, e a Socket foi a primeira a analisar os 40 pacotes iniciais e o malware. Depois, a Aikido encontrou mais 147 pacotes e também o pacote da Crowdstrike. Na prática, foi a Step que primeiro percebeu que o malware era um worm autorreplicante. É interessante ver várias organizações tendo papéis diferentes de forma independente

    • Parece que vários desenvolvedores descobriram isso quase ao mesmo tempo, e Step e Socket citam pessoas diferentes. No fim, os fornecedores de segurança do setor detectaram isso cada um à sua maneira, seja com análise de código por IA (Socket, Aikido), seja com monitoramento de pipeline via eBPF (Step)

    • Se tantos fornecedores detectaram isso de forma independente, fica a dúvida se não poderiam compartilhar essa tecnologia diretamente com o npm para bloquear já no cadastro os pacotes maliciosos. Mas aí eles não conseguiriam vender sistemas de alerta precoce, então talvez por isso não disponibilizem isso

    • O artigo do OP cita diretamente que “@franky47 descobriu esse fenômeno e logo avisou a comunidade por meio de uma issue no GitHub

  • Acho bem espirituoso o nome escolhido pelo atacante, 'Shai Hulud': deram o nome de um verme gigante a um malware do tipo worm. O bundle.js principal também é gigantesco, com 3,6 MB. Até as variantes do malware ficaram enormes, bem no estilo npm

    • Tenho a impressão de que em breve algum ataque à cadeia de suprimentos vai acabar atraindo acidentalmente outro ataque à cadeia de suprimentos

    • O malware também segue a Lei de Moore: em 1991 o tequila virus tinha 2,6 KB; hoje já estamos em vários MB