- O Seattle Times evitou por acaso o ataque Shai-Hulud 2.0, mas partiu do princípio de que sorte não pode ser estratégia de segurança e adotou defesas no lado do cliente
- Melhorias do npm como Trusted publishing / provenance / tokens granulares fortalecem o lado da “publicação”, mas ainda deixam uma lacuna: não impedem a execução de código malicioso no momento de “instalação/atualização”
- O pnpm usa o mesmo registro do npm, mas adiciona controles que dificultam a execução de pacotes maliciosos na etapa de consumo (install/update)
- No piloto, aplicaram 3 controles do pnpm para bloquear separadamente vetores como execução de scripts de lifecycle, instalação imediata da release mais recente e rebaixamento de confiança
- Exceções são tratadas não como falha, mas como parte do design, com o objetivo de operar em defense-in-depth, documentando as exceções enquanto as demais camadas continuam protegendo
Contexto do incidente e premissas
- Em novembro de 2025, ocorreu um caso em que um worm autorreplicante no npm infectou 796 pacotes e se espalhou em um universo de 132 milhões de downloads mensais
- O ataque usou scripts
preinstall para roubar credenciais, instalar backdoors persistentes e, em alguns ambientes, até apagar o ambiente de desenvolvimento
- O motivo de nossa organização não ter sido afetada não foi uma defesa forte, mas o acaso de não ter executado
npm install/npm update durante o período do ataque
- Em uma organização de notícias, a confiança é central, e um comprometimento da cadeia de suprimentos pode expor dados de clientes, credenciais de funcionários, infraestrutura de produção e código-fonte, além de gerar altos custos de recuperação e notificação
Equipe e contexto de adoção
- O Seattle Times usa npm há muito tempo como gerenciador de pacotes padrão, e já houve experimentos com Yarn, mas eles não se consolidaram
- O motivo para adotar pnpm é a presença de controles de segurança no lado do cliente que complementam as melhorias no nível do registro
- O pnpm foi considerado uma opção viável de migração por ser um drop-in replacement que usa o mesmo registro, os mesmos comandos e o mesmo fluxo de trabalho
- Não se trata de um estudo de caso concluído, mas do compartilhamento dos problemas e do raciocínio de uma equipe real que está começando agora a lidar com segurança da cadeia de suprimentos
Por que controles no lado do cliente são necessários
- As melhorias de segurança do npm realmente tornaram mais difícil a publicação de pacotes maliciosos após o comprometimento de uma conta
- Essas melhorias protegem o lado da “publicação”, mas não impedem a instalação em si de um pacote malicioso na etapa de “consumo”
- Em
npm install/npm update, lifecycle scripts (preinstall/postinstall etc.) podem executar código arbitrário com privilégios do desenvolvedor antes de qualquer avaliação de segurança do pacote
- Esses scripts podem acessar credenciais do npm/GitHub/AWS/BD, código-fonte, infraestrutura em nuvem e todo o sistema de arquivos
- Ataques como o Shai-Hulud exploram exatamente essa estrutura: se a conta do mantenedor for comprometida, scripts maliciosos podem ser executados no momento da instalação, antes que a comunidade consiga detectar o problema
- As melhorias do npm no lado da publicação e os controles do pnpm no lado do consumo foram combinados como defesas complementares, formando uma estratégia de “defense-in-depth”
As 3 camadas aplicadas
- No piloto, foram usados em conjunto 3 controles voltados a vetores de ataque diferentes
- Cada controle tem uma válvula de escape para exceções realistas e foi desenhado partindo da premissa de que, em produção, exceções serão necessárias
Control 1: Gerenciamento de Lifecycle Scripts
- No pnpm, por padrão, é possível bloquear lifecycle scripts, permitindo que a instalação prossiga com um aviso
- Como havia receio de que avisos fossem ignorados, foi escolhido
strictDepBuilds: true, forçando a instalação a falhar imediatamente se houver scripts
- Exemplo de configuração no
pnpm-workspace.yaml com os campos a seguir
strictDepBuilds: true
onlyBuiltDependencies: lista de permissões para pacotes com scripts de build necessários
ignoredBuiltDependencies: lista de bloqueio (ou ignorados) para pacotes com scripts de build desnecessários
- “Scripts necessários” foram definidos como ações como compilação de extensões nativas ou linkagem de bibliotecas dependentes de plataforma
- “Scripts desnecessários” foram definidos como otimizações ou configurações opcionais que, na forma de uso da equipe, não afetam a funcionalidade
- A falha na instalação força as etapas seguintes
- o pnpm informa claramente quais pacotes têm scripts
- investigar e entender o que os scripts fazem
- decidir conscientemente, com julgamento humano, permitir ou bloquear, e documentar essa decisão
- A equipe do pnpm está considerando tornar
strictDepBuilds: true o padrão na v11, além de revisar os nomes da sintaxe de allow/deny
Control 2: Cooldown de Release
- Versões publicadas recentemente ficam bloqueadas para instalação por um período de cooldown, dando tempo para a comunidade detectar e remover pacotes maliciosos
- Exemplo de configuração no
pnpm-workspace.yaml com os campos a seguir
minimumReleaseAge: <duration-in-minutes>
minimumReleaseAgeExclude: lista de exceções para casos como hotfixes urgentes
- É preciso abandonar o hábito de pensar que “o mais recente é o melhor” e adotar a ideia de que, do ponto de vista da cadeia de suprimentos, algo um pouco mais antigo pode ser mais seguro
- No ataque de setembro de 2025 (16 pacotes, incluindo debug e chalk), a remoção ocorreu em cerca de 2,5 horas; no Shai-Hulud 2.0, de novembro de 2025, levou cerca de 12 horas
- Dependendo da tolerância a risco da organização, o cooldown pode ser de horas, dias ou semanas, e qualquer uma dessas formas teria bloqueado esse ataque
- Isso combina bem com a realidade de organizações que já não conseguem usar sempre a versão mais recente, então o cooldown não atrapalha muito o trabalho
- Quando for realmente necessário, como em um patch de segurança ou bug crítico, a exceção pode ser liberada após revisão
Control 3: Política de Confiança
- Se surgir uma versão publicada com autenticação mais fraca do que a versão anterior, a instalação é bloqueada
- Isso é tratado como sinal de um caso em que a conta do mantenedor foi comprometida e a publicação foi feita da máquina do atacante, e não do CI/CD oficial
- Exemplo de configuração no
pnpm-workspace.yaml com os campos a seguir
trustPolicy: no-downgrade
trustPolicyExclude: lista de exceções para casos como migração de CI/CD
- O texto explica que o npm acompanha 3 níveis de confiança na publicação de pacotes, do mais forte para o mais fraco
- Trusted Publisher: baseado em GitHub Actions + OIDC + npm provenance
- Provenance: attestation assinada no CI/CD
- No Trust Evidence: publicação com username/password ou token
- Se a nova versão tiver nível de confiança inferior ao da anterior, a instalação falha
- No ataque de agosto de 2025 da s1ngularity, em que o atacante publicou localmente uma versão maliciosa sem acesso ao CI/CD e sem provenance, esse controle teria bloqueado a instalação
- Como exemplos legítimos de rebaixamento, são citados a entrada de um novo mantenedor, migração de CI/CD e hotfix manual durante indisponibilidade do CI/CD; nesses casos, após investigação, o pacote entra na lista de exceções
- Esse recurso é novo, adicionado ao pnpm em novembro de 2025, e ainda estão aprendendo com que frequência rebaixamentos legítimos realmente acontecem
Exemplo de funcionamento combinado das camadas: patch de vulnerabilidade no React
- Em um cenário em que é preciso aplicar imediatamente o patch de uma vulnerabilidade crítica em React Server Components, divulgado em dezembro de 2025
- Normalmente, o cooldown bloquearia a “instalação de uma versão recém-publicada”, mas, por se tratar de um patch crítico de segurança, não dá para esperar
- Nesse caso, adiciona-se a versão específica do React em
minimumReleaseAgeExclude, mas só depois de revisar o aviso da vulnerabilidade e a legitimidade do patch
- Mesmo com a exceção aplicada, as outras camadas continuam protegendo
- O React normalmente não tem lifecycle scripts, então, se um script aparecer na versão do patch, isso vira imediatamente um sinal suspeito e pode ser bloqueado
- Se um atacante roubar credenciais e publicar localmente um “patch”, ele pode ser bloqueado por rebaixamento do nível de confiança
- A ideia é tratar a exceção não como “falha de segurança”, mas como um design em que, mesmo quando uma camada é contornada, as outras continuam ativas, eliminando o ponto único de falha
Resultado da aplicação piloto
- Foi feito um PoC aplicando os 3 controles em um serviço de backend
- O tempo total de preparação, incluindo investigação, entendimento e definição de acesso, foi de apenas algumas horas
- O pnpm identificou 3 pacotes com lifecycle scripts
- esbuild: otimiza o início do CLI em milissegundos, mas a equipe usa apenas a API JavaScript, então foi considerado desnecessário
- @firebase/util: faz configuração automática do SDK cliente, mas a equipe usa apenas o SDK de servidor, então foi considerado desnecessário
- protobufjs: faz verificação de compatibilidade de esquema, mas é usado apenas como dependência transitiva e foi considerado desnecessário para o caso de uso da equipe
- Após consultar a documentação e analisar os scripts (incluindo uso de IA para ajudar a interpretar os scripts), concluíram que os 3 eram desnecessários no contexto da equipe e os bloquearam
- Não houve impacto funcional
- O atrito (friction) é uma funcionalidade intencional: um mecanismo que impede confiar implicitamente no código executado no ambiente
- Quando uma nova dependência tiver scripts, a expectativa é gastar cerca de 15 minutos em revisão e documentação
Lições aprendidas durante a operação
- A combinação das camadas no lado do cliente com as melhorias do npm no lado da publicação fez com que o defense-in-depth realmente funcionasse na prática
- Mesmo ao aplicar exceções, ainda restam outras camadas, o que reduz a insegurança em relação a essas exceções
- A mudança de modelo mental de “conveniência em primeiro lugar” para “segurança em primeiro lugar” leva tempo, mas depois se torna natural
- Isso é viável na prática até para organizações de porte médio sem equipe de segurança dedicada
- A trust policy é um recurso com apenas algumas semanas de vida, então ainda é preciso aprender mais sobre a frequência de rebaixamentos legítimos e o operacional do dia a dia
- Há planos de expandir isso em breve para outras bases de código, o que deve trazer mais dados em aplicações com grafos de dependência diferentes
Dicas de adoção para outras equipes
- A recomendação é começar por um projeto e usar isso para aprender o fluxo de trabalho e os pontos de atrito
- Como exceções podem ser necessárias em lifecycle scripts, release cooldown e trust downgrade, projete com exceções em mente desde o início
- Recomenda-se usar
strictDepBuilds: true desde o primeiro dia, forçando falha na instalação em vez de depender de avisos
- Documente todas as exceções para deixar um rastro de auditoria e facilitar revisões futuras
- Lembre-se de que a exceção em uma camada ainda preserva a proteção das demais
1 comentários
pnpm! pnpm! pnpm! Realmente dá para confiar.