- Vários pacotes npm que incluíam o pacote open source @ctrl/tinycolor foram infectados por versões maliciosas; a causa foi o roubo de token npm por meio de um workflow do GitHub Actions em um repositório colaborativo
- O invasor usou um token npm com permissões amplas para publicar código malicioso em cerca de 20 pacotes, e entre eles o @ctrl/tinycolor tinha grande impacto por registrar 2 milhões de downloads semanais
- As versões infectadas executavam um payload malicioso na etapa de postinstall, e as equipes de segurança do GitHub e do npm responderam rapidamente com remoção e limpeza
- O autor preparou um plano de segurança reforçado para evitar recorrência, incluindo migração para Trusted Publishing (OIDC), minimização de permissões de tokens, obrigatoriedade de 2FA e uso de recursos do pnpm
- Este incidente mostra a fragilidade da segurança da cadeia de suprimentos de software e evidencia a necessidade de melhorar os recursos de segurança e mudar as práticas de segurança em todo o ecossistema npm
TL;DR
- Um workflow malicioso do GitHub Actions foi enviado a um repositório compartilhado e roubou um token npm
- Com esse token, o invasor publicou versões maliciosas de 20 pacotes, e entre eles o @ctrl/tinycolor teve maior alcance por causa do alto volume de downloads
- A conta pessoal e os repositórios não foram comprometidos diretamente, e não houve phishing nem instalação local de malware
- Graças à resposta rápida das equipes de segurança do GitHub/npm, as versões maliciosas foram removidas e depois versões limpas foram republicadas para limpar o cache
Como descobri o incidente (How I Found Out)
- Na tarde de 15 de setembro, o membro da comunidade Wes Todd avisou sobre o problema por DM no Bluesky
- As equipes de segurança do GitHub/npm já estavam organizando a lista de pacotes afetados e iniciando a remoção
- Como pista inicial, foi compartilhado o nome de branch malicioso 'Shai-Hulud', tirado do nome do verme da areia do universo de Dune
O que realmente aconteceu (What Actually Happened)
- Havia um colaborador de longa data no repositório angulartics2 que ainda mantinha permissões de admin
- O token npm armazenado nesse repositório foi roubado por um workflow malicioso do GitHub Actions
- Com esse token, o invasor publicou cerca de 20 pacotes, incluindo @ctrl/tinycolor
- As equipes de segurança do GitHub/npm removeram rapidamente as versões maliciosas, e o autor republicou novas versões confiáveis
Impacto (Impact)
- Ao instalar uma versão maliciosa, o script de postinstall era executado, gerando risco de segurança
- Usuários afetados são orientados a consultar as instruções imediatas de resposta da StepSecurity
Ambiente de publicação e plano de resposta (Publishing Setup & Interim Plan)
- Antes, a publicação automática era feita com a combinação semantic-release + GitHub Actions
- O recurso de provenance do npm era usado, mas não conseguia impedir um invasor com um token válido
- No futuro, o plano é adotar Trusted Publishing (OIDC) para eliminar tokens estáticos
- No momento, todos os tokens foram revogados, a 2FA foi tornada obrigatória, apenas tokens com permissões granulares são permitidos, e medidas extras como avaliação do recurso minimumReleaseAge do pnpm estão sendo aplicadas
Melhorias ideais (Publishing Wishlist)
- É necessário oferecer, no nível da conta npm, uma opção para forçar Trusted Publishing baseado em OIDC
- Também é necessário bloquear publicações sem provenance e oferecer suporte a integração completa entre semantic-release e OIDC
- Há o desejo de que a interface do GitHub ofereça uma função de publicação com aprovação manual baseada em 2FA
- Recursos de proteção no nível de GitHub Environments deveriam estar disponíveis mesmo sem assinatura Pro
- Na página de pacotes do npm, deveria haver indicação da presença de script postinstall e divulgação do motivo de versões removidas
1 comentários
Comentários do Hacker News
Ainda havia segredos do GitHub Actions nesse repositório, ou seja, um token do npm com permissões amplas de publicação
Uma das vantagens do Trusted Publishing é justamente não precisar mais usar tokens de publicação de longa duração
Agora só se usam tokens gerados temporariamente na VM de CI, e eles são válidos por apenas 15 minutos
Isso já está sendo adotado em vários ecossistemas, como PyPI, npm, Cargo e Homebrew
Como o processo de publicação também acaba ficando um pouco mais fácil, recomendo que todo mundo experimente
Se a documentação ainda parecer pouco clara, fiquem à vontade para pedir ajuda
Os mantenedores dos ecossistemas parecem bastante interessados em ver esse recurso se espalhar
Veja a documentação oficial do Trusted Publishing
Só fiquei sabendo agora que o npm já permite Trusted Publishing
Notícia relacionada
Vou configurar isso neste fim de semana
Seria bom se agora houvesse um sinalizador no repositório indicando que o projeto usa esse tipo de recurso
Assim, daria para bloquear facilmente dependências que não usam isso
Acho que o ponto de incluir MFA (autenticação multifator) no processo de deploy automático não está recebendo atenção suficiente
Não haveria problema em publicar via workflow de CI e confirmar a publicação com um prompt de MFA, mas da última vez que vi isso exigia abrir um túnel HTTPS para fornecer o código, o que complicava bastante
Seria ótimo se o npm ou o GitHub oferecessem diretamente uma forma fácil de fornecer e confirmar o código MFA durante a CI
A publicação de pacotes tem 2 etapas: enviar o pacote para o npmjs e torná-lo de fato público para os usuários
Hoje essas duas etapas ficam acopladas em uma única operação
Na minha opinião, isso deveria ser separado, de modo que o sistema de CI só faça o build e o upload automaticamente
Para realmente liberar o pacote enviado, uma pessoa teria que entrar manualmente no site do npmjs e fazer a publicação com MFA
Na verdade, fico pensando se essa própria ideia de publicar pacote não é desnecessária
Se o VCS é a “fonte real”, então por que não usar isso diretamente sem um processo separado de publicação?
A linguagem Go realmente funciona assim
Você importa pacotes diretamente por URL, e o versionamento é gerenciado por tags
Assim, basta confiar no VCS, o que reduz a superfície adicional de ataque
Em vez de precisar comparar arquivos de arquivo separados, basta verificar os commits
O problema é que, se o repositório for movido, o caminho de importação muda, mas isso também pode ser visto como uma vantagem
Fora isso, não vejo muito benefício em uma etapa separada de publicação
Parece um resquício da época em que se subiam arquivos tar por FTP
No passado, trabalhei em um repositório compartilhado chamado angulartics2
Ainda havia lá um segredo do GitHub Actions contendo um token do npm com permissões amplas de publicação
Um dos colaboradores também tinha permissões sobre vários projetos, e suspeito que essa seja a razão de vários pacotes terem sido afetados ao mesmo tempo
Uma nova branch chamada Shai-Hulud foi enviada com force push junto com um workflow malicioso do GitHub Actions
Como era um colaborador com privilégios de administrador, o workflow rodou imediatamente sem precisar de revisão, e o token do npm vazou
Com o token vazado, versões maliciosas foram publicadas em 20 pacotes
A maioria não é de pacotes muito usados, mas @ctrl/tinycolor é um pacote popular com cerca de 2 milhões de downloads por semana
O que ainda não entendo é como foi possível publicar até no tinycolor com o token do npm do repositório angulartics2
Eu também tenho privilégios de administrador no repositório npm de outra pessoa, e fui eu quem fez a maior parte dos releases recentes
Depois que virei administrador, acabei fazendo mais commits em meu nome porque quis aproveitar para corrigir vários problemas antigos
Eu já estava quase decidido a publicar o pacote via GitHub Actions, mas sempre tive receio de acabar liberando algo que não estava na master ao fazer deploy manual com 2FA
Por causa dessas preocupações, fui adiando a conversa com os outros administradores, e vendo o que aconteceu agora, parece até que foi melhor ter adiado
Não sei qual é a resposta certa, mas certamente não parece uma boa ideia deixar credenciais nas mãos de terceiros
Desculpe se não fui claro
Esse token tinha permissões globais de publicação sobre todos os meus pacotes no npm
Tenho defendido releases manuais nos últimos 10 anos
Sempre recebi muita resistência, mas hoje isso já não parece uma ideia tão estranha
Eu sei que CI/CD é elegante, mas olhando para este caso e para o problema recente da CF, há cada vez mais evidências de que a automação pode facilitar problemas graves
Quando trabalhei no BigBank, já houve deploy em produção com pelo menos cinco pessoas de prontidão e um monte de procedimento, mas ao menos sabíamos exatamente o que estava sendo publicado
Não por causa de GitHub Actions ou scripts automáticos de release, mas acho muito mais seguro voltar ao modo antigo de fazer build manualmente, assinar, enviar o tarball e validar
Sistemas de distribuição, como os de empacotamento de distribuições como Debian, ainda passam por etapas adicionais de validação, e isso também ajuda a explicar por que a internet inteira não foi comprometida no caso do xz
No mínimo, deveria ser obrigatório que uma pessoa assinasse manualmente os binários antes de um release ser publicado
Como também existe o problema de um atacante se adicionar como mantenedor e assinar com a própria chave, seria ainda mais seguro combinar isso com gerenciamento de chaves confiáveis, como fazem sistemas de empacotamento de distribuições
Se o meu modelo de ameaça parte da ideia de que “basta uma conta do GitHub ou uma chave de API vazar para comprometer todos os usuários”, então eu realmente deveria reconsiderar se isso é razoável
Usar 2FA na publicação é bom, mas seria bem mais seguro exigir a concordância de vários autores por meio de assinaturas criptográficas
Não deveria bastar comprometer uma única pessoa para o ataque dar certo
Muitos pacotes têm apenas um autor
Exigir assinaturas de vários autores também é bom, mas se houver verificação de assinatura de qualquer forma em commits, tags e artefatos, a maioria dos ataques já pode ser evitada
Empacotamentos de distribuições têm excelente suporte para verificação de assinaturas, mas os gerenciadores de pacotes de linguagens ainda carecem desse tipo de mecanismo
Por exemplo, todo o processo oficial de release do runc é assinado com chaves dos mantenedores, guardadas em Yubikeys e similares
Os sistemas de distribuição também mantêm seus próprios keyrings para verificar fontes oficiais e binários
Se algo assim existisse, acho que este ataque teria sido bloqueado em várias etapas
Dá para fazer o build direto na CI, mas no fim precisa haver uma etapa em que o mantenedor assina manualmente
Se um gerenciador de pacotes de linguagem não oferece esse fluxo, o Trusted Publishing acaba sendo a alternativa menos ruim
Mas se a conta do GitHub for comprometida, por exemplo via roubo de cookie, a publicação ainda pode acontecer imediatamente
No GitHub há configurações de segurança como timeout para Trusted Publishing, mas um invasor também pode desativá-las
Mesmo que minha conta seja comprometida, numa distribuição mudanças assinadas com uma chave que não seja a minha não seriam aceitas, então isso é relativamente mais seguro
Observação: sou da SUSE, mas gostaria de ver mais suporte à verificação de artefatos em openSUSE, Arch, Gentoo etc.
Links relacionados:
runc.keyring
keyring_validate.sh
release_sign.sh
runc.keyring do openSUSE
Eu odeio tokens
Tokens são basicamente senhas estáticas
Acho que precisamos de um método de autenticação mais adequado
Por exemplo, usar o GitHub como provedor de tokens para a AWS me parece um caminho mais sensato
Integração GitHub-AWS com OIDC
Mas isso é mais uma exceção do que a regra
O fluxo OIDC entre máquinas pode ser seguro se for bem implementado, mas a configuração é complexa demais
E no fim OIDC também passa a sensação de ser “um token mais complicado”
Em um ambiente automatizado sem checagem humana, sempre vai existir algo que pode vazar em algum ponto, seja o token ou o gerador do token
Mesmo neste caso do worm, OIDC não teria sido uma solução fundamental
Se o workflow do GitHub tivesse sido comprometido, com ou sem OIDC uma identidade temporária ainda seria injetada no ambiente
No fim, o importante é ter um sistema em que usuários não autorizados não consigam executar workflows com acesso a segredos
Se a ideia é granularidade de permissões, talvez reduzir o escopo do token seja até mais eficaz do que OIDC
A ideia original dos tokens é justamente ter tempo de vida limitado e escopo de autorização limitado
Só que, na maioria dos casos, eles não são usados assim e acabam virando senhas estáticas
Existem alternativas como oauth e biscuits, que permitem restrições mais detalhadas de permissão, mas quase ninguém usa isso na prática
Trusted Publishing agora já é suportado por vários registros de pacotes, incluindo o npm
Notícia relacionada
Como outros já mencionaram, tokens só deveriam ser emitidos com vida curta ou depois de autenticação manual, como MFA ou passphrase
Usar mTLS (certificado de cliente TLS) parece mais próximo da direção correta
Alguém conhece alguma ferramenta/script público para verificar pacotes vulneráveis do npm?
Pelo que vi, a página da StepSecurity não parece ter isso
Não resolve tudo, mas adotar o provenance-action também é uma boa ideia
provenance-action
Para problemas já conhecidos, o básico é usar
npm auditO que eu queria dizer é que eu me preocupava com erros no publish local, como estar na branch errada ou esquecer alguma etapa do build
Fico pensando no que aconteceria se um job de CI fizesse force push de alterações em partes profundas do histórico do git
A situação atual já não está funcionando direito
Claro, dá para elogiar vantagens técnicas como tokens OIDC e soluções de zero trust
Mas muitos dos mantenedores de bibliotecas npm com milhões de downloads só vão começar a se preocupar com segurança depois de serem invadidos ou de o próprio npm bloquear a distribuição
E também aparecem propostas inviáveis como “eliminar todas as dependências e ficar só com a biblioteca padrão”
Reduzir dependências é bom, mas não resolve em nada o problema que já existe
Na prática, ou dez mil, cem mil pessoas abandonam o npm e reescrevem tudo, ou o npm impõe regras como 2FA e OIDC principalmente para pacotes muito baixados e bloqueia a publicação de quem não cumprir
É bem claro qual das duas opções é mais viável na prática
Caso contrário, a reputação do npm vai despencar e acabaremos no cenário do XKCD 927